[
  {
    "path": ".github/workflows/main.yml",
    "content": "name: CI\n\non: [push, pull_request]\n\njobs:\n  rspec:\n    runs-on: ubuntu-latest\n\n    services:\n      clickhouse:\n        image: clickhouse/clickhouse-server:22.9\n        ports: \n          - 8123:8123\n\n    strategy:\n      matrix:\n        ruby-version: [3.1, '3.0', 2.7]\n\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Set up Ruby ${{ matrix.ruby-version }}\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: ${{ matrix.ruby-version }}\n\n      - name: Setup v1\n        run: make faraday1 bundle\n\n      - name: Setup v2\n        run: make faraday2 bundle\n\n      - name: Run tests with faraday v.1 JSON\n        run: make faraday1 rspec\n      - name: Run tests with faraday v.2 JSON\n        run: make faraday2 rspec\n      - name: Run tests with faraday v.1 OJ\n        run: make faraday1 oj rspec\n      - name: Run tests with faraday v.2 OJ\n        run: make faraday2 oj rspec\n\n  rubocop:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Set up Ruby 2.7\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: 2.7\n          bundler-cache: true # 'bundle install' and cache\n\n      - name: Run Rubocop\n        run: bundle exec rubocop\n"
  },
  {
    "path": ".gitignore",
    "content": "/.bundle/\n/.yardoc\n/.idea/\n/_yardoc/\n/coverage/\n/pkg/\n/spec/reports/\n/log/*\n/tmp/*\n/*.gem\n\n!/log/.keep\n!/tmp/.keep\n\n# rspec failure tracking\n.rspec_status\n"
  },
  {
    "path": ".rspec",
    "content": "--format documentation\n--color\n--require spec_helper\n"
  },
  {
    "path": ".rubocop.yml",
    "content": "require:\n  - rubocop-performance\n\nAllCops:\n  AutoCorrect: false\n  SuggestExtensions: false\n  Exclude:\n    - 'click_house.gemspec'\n    - 'bin/*'\n    - 'lib/click_house/benchmark/*'\n    - 'spec/**/*'\n    - 'vendor/**/*'\n  TargetRubyVersion: 2.7\nBundler/OrderedGems:\n  Enabled: false\n\n# ============================== Gemspec ======================\nGemspec/DeprecatedAttributeAssignment:\n  Enabled: true\nGemspec/RequireMFA: # new in 1.23\n  Enabled: true\n\n# =============================== Performance =======================\nPerformance/AncestorsInclude:\n  Enabled: true\nPerformance/BigDecimalWithNumericArgument:\n  Enabled: true\nPerformance/RedundantSortBlock:\n  Enabled: true\nPerformance/RedundantStringChars:\n  Enabled: true\nPerformance/ReverseFirst:\n  Enabled: true\nPerformance/SortReverse:\n  Enabled: true\nPerformance/Squeeze:\n  Enabled: true\nPerformance/StringInclude:\n  Enabled: true\nPerformance/Sum:\n  Enabled: true\nPerformance/ArraySemiInfiniteRangeSlice:\n  Enabled: true\nPerformance/BlockGivenWithExplicitBlock:\n  Enabled: true\nPerformance/CollectionLiteralInLoop:\n  Enabled: true\nPerformance/ConstantRegexp:\n  Enabled: true\nPerformance/MethodObjectAsBlock:\n  Enabled: false\nPerformance/RedundantEqualityComparisonBlock:\n  Enabled: true\nPerformance/RedundantSplitRegexpArgument:\n  Enabled: true\nPerformance/MapCompact:\n  Enabled: true\nPerformance/ConcurrentMonotonicTime: # new in 1.12\n  Enabled: true\nPerformance/StringIdentifierArgument: # new in 1.13\n  Enabled: true\n\n# ============================== Metrics ============================\nMetrics/ClassLength:\n  Max: 180\nMetrics/BlockLength:\n  Enabled: true\nMetrics/MethodLength:\n  Max: 25\nMetrics/AbcSize:\n  Max: 40\n\n# ============================== Naming =============================\nNaming/PredicateName:\n  ForbiddenPrefixes:\n    - is_\nNaming/FileName:\n  Enabled: true\n  Exclude:\n    - 'Gemfile'\nNaming/MethodParameterName:\n  Enabled: false\nNaming/AccessorMethodName:\n  Enabled: false\nNaming/InclusiveLanguage:\n  Enabled: true\nNaming/BlockForwarding: # new in 1.24\n  Enabled: true\n\n# ============================== Layout =============================\nLayout/LineLength:\n  Max: 140\nLayout/HashAlignment:\n  EnforcedHashRocketStyle: key\n  EnforcedColonStyle: key\nLayout/ParameterAlignment:\n  EnforcedStyle: with_fixed_indentation\nLayout/CaseIndentation:\n  EnforcedStyle: case\n  IndentOneStep: false\nLayout/MultilineMethodCallIndentation:\n  Enabled: true\n  EnforcedStyle: indented\nLayout/SpaceBeforeBlockBraces:\n  EnforcedStyle: space\n  EnforcedStyleForEmptyBraces: space\nLayout/EmptyLines:\n  Enabled: true\nLayout/EmptyLineAfterMagicComment:\n  Enabled: false\nLayout/EmptyLinesAroundBlockBody:\n  Enabled: true\nLayout/EndAlignment:\n  EnforcedStyleAlignWith: variable\nLayout/FirstHashElementIndentation:\n  EnforcedStyle: consistent\nLayout/HeredocIndentation:\n  Enabled: false\nLayout/RescueEnsureAlignment:\n  Enabled: false\nLayout/EmptyLinesAroundAttributeAccessor:\n  Enabled: true\nLayout/SpaceAroundMethodCallOperator:\n  Enabled: true\nLayout/SpaceBeforeBrackets:\n  Enabled: true\nLayout/LineEndStringConcatenationIndentation:\n  Enabled: true\nLayout/LineContinuationLeadingSpace: # new in 1.31\n  Enabled: true\nLayout/LineContinuationSpacing: # new in 1.31\n  Enabled: true\n\n# ============================== Style ==============================\nStyle/RescueModifier:\n  Enabled: true\nStyle/PercentLiteralDelimiters:\n  PreferredDelimiters:\n    default: '[]'\n    '%i':    '[]'\n    '%w':    '[]'\n  Exclude:\n    - 'config/routes.rb'\nStyle/StringLiterals:\n  Enabled: true\nStyle/AsciiComments:\n  Enabled: false\nStyle/Copyright:\n  Enabled: false\nStyle/SafeNavigation:\n  Enabled: false\nStyle/Lambda:\n  Enabled: false\nStyle/Alias:\n  Enabled: true\n  EnforcedStyle: prefer_alias_method\nStyle/ClassAndModuleChildren:\n  Enabled: true\n  EnforcedStyle: nested\nStyle/TrailingCommaInArrayLiteral:\n  Enabled: true\n  EnforcedStyleForMultiline: no_comma\nStyle/RescueStandardError:\n  Enabled: true\n  EnforcedStyle: explicit\nStyle/InverseMethods:\n  AutoCorrect: false\n  Enabled: true\nStyle/IfUnlessModifier:\n  Enabled: false\nStyle/SpecialGlobalVars:\n  Enabled: false\nStyle/BlockComments:\n  Enabled: false\nStyle/GuardClause:\n  Enabled: false\nStyle/TrailingCommaInHashLiteral:\n  Enabled: false\nStyle/ExponentialNotation:\n  Enabled: true\nStyle/HashEachMethods:\n  Enabled: true\nStyle/HashTransformKeys:\n  Enabled: true\nStyle/HashTransformValues:\n  Enabled: true\nStyle/RedundantFetchBlock:\n  Enabled: true\nStyle/RedundantRegexpCharacterClass:\n  Enabled: true\nStyle/RedundantRegexpEscape:\n  Enabled: true\nStyle/SlicingWithRange:\n  Enabled: true\nStyle/AccessorGrouping:\n  Enabled: false\nStyle/ArrayCoercion:\n  Enabled: true\nStyle/BisectedAttrAccessor:\n  Enabled: true\nStyle/CaseLikeIf:\n  Enabled: true\nStyle/HashAsLastArrayItem:\n  Enabled: true\nStyle/HashLikeCase:\n  Enabled: true\nStyle/RedundantAssignment:\n  Enabled: true\nStyle/RedundantFileExtensionInRequire:\n  Enabled: true\nStyle/ExplicitBlockArgument:\n  Enabled: true\nStyle/GlobalStdStream:\n  Enabled: true\nStyle/OptionalBooleanParameter:\n  Enabled: true\nStyle/SingleArgumentDig:\n  Enabled: true\nStyle/StringConcatenation:\n  Enabled: true\nStyle/ClassEqualityComparison:\n  Enabled: true\nStyle/CombinableLoops:\n  Enabled: true\nStyle/KeywordParametersOrder:\n  Enabled: false\nStyle/RedundantSelfAssignment:\n  Enabled: true\nStyle/SoleNestedConditional:\n  Enabled: true\nStyle/ArgumentsForwarding:\n  Enabled: true\nStyle/CollectionCompact:\n  Enabled: true\nStyle/DocumentDynamicEvalDefinition:\n  Enabled: false\nStyle/NegatedIfElseCondition:\n  Enabled: true\nStyle/NilLambda:\n  Enabled: true\nStyle/SwapValues:\n  Enabled: true\nStyle/RedundantArgument:\n  Enabled: true\nStyle/HashExcept:\n  Enabled: true\nStyle/EndlessMethod:\n  Enabled: true\nStyle/IfWithBooleanLiteralBranches:\n  Enabled: true\nStyle/HashConversion:\n  Enabled: true\nStyle/Documentation:\n  Enabled: false\nStyle/InPatternThen:\n  Enabled: true\nStyle/MultilineInPatternThen:\n  Enabled: true\nStyle/QuotedSymbols:\n  Enabled: true\nStyle/StringChars:\n  Enabled: true\nStyle/EmptyHeredoc: # new in 1.32\n  Enabled: true\nStyle/EnvHome: # new in 1.29\n  Enabled: true\nStyle/FetchEnvVar: # new in 1.28\n  Enabled: true\nStyle/FileRead: # new in 1.24\n  Enabled: true\nStyle/FileWrite: # new in 1.24\n  Enabled: true\nStyle/MagicCommentFormat: # new in 1.35\n  Enabled: true\nStyle/MapCompactWithConditionalBlock: # new in 1.30\n  Enabled: true\nStyle/MapToHash: # new in 1.24\n  Enabled: true\nStyle/NestedFileDirname: # new in 1.26\n  Enabled: true\nStyle/NumberedParameters: # new in 1.22\n  Enabled: true\nStyle/NumberedParametersLimit: # new in 1.22\n  Enabled: true\nStyle/ObjectThen: # new in 1.28\n  Enabled: true\nStyle/OpenStructUse: # new in 1.23\n  Enabled: true\nStyle/OperatorMethodCall: # new in 1.37\n  Enabled: true\nStyle/RedundantEach: # new in 1.38\n  Enabled: true\nStyle/RedundantInitialize: # new in 1.27\n  Enabled: true\nStyle/RedundantSelfAssignmentBranch: # new in 1.19\n  Enabled: true\nStyle/RedundantStringEscape: # new in 1.37\n  Enabled: true\nStyle/SelectByRegexp: # new in 1.22\n  Enabled: true\n\n# ============================== Security ==============================\nSecurity/CompoundHash: # new in 1.28\n  Enabled: true\nSecurity/IoMethods: # new in 1.22\n  Enabled: true\n\n# ============================== Lint ==============================\nLint/DuplicateMethods:\n  Enabled: false\nLint/AmbiguousOperator:\n  Enabled: false\nLint/DeprecatedOpenSSLConstant:\n  Enabled: true\nLint/MixedRegexpCaptureTypes:\n  Enabled: true\nLint/RaiseException:\n  Enabled: true\nLint/StructNewOverride:\n  Enabled: true\nLint/DuplicateElsifCondition:\n  Enabled: true\nLint/BinaryOperatorWithIdenticalOperands:\n  Enabled: true\nLint/DuplicateRescueException:\n  Enabled: true\nLint/EmptyConditionalBody:\n  Enabled: true\nLint/FloatComparison:\n  Enabled: true\nLint/MissingSuper:\n  Enabled: false\nLint/OutOfRangeRegexpRef:\n  Enabled: true\nLint/SelfAssignment:\n  Enabled: true\nLint/TopLevelReturnWithArgument:\n  Enabled: true\nLint/UnreachableLoop:\n  Enabled: true\nLayout/BeginEndAlignment:\n  Enabled: true\nLint/ConstantDefinitionInBlock:\n  Enabled: true\nLint/DuplicateRequire:\n  Enabled: true\nLint/EmptyFile:\n  Enabled: true\nLint/HashCompareByIdentity:\n  Enabled: true\nLint/IdentityComparison:\n  Enabled: true\nLint/RedundantSafeNavigation:\n  Enabled: true\nLint/TrailingCommaInAttributeDeclaration:\n  Enabled: true\nLint/UselessMethodDefinition:\n  Enabled: true\nLint/UselessTimes:\n  Enabled: true\nLint/DuplicateBranch:\n  Enabled: true\nLint/DuplicateRegexpCharacterClassElement:\n  Enabled: true\nLint/EmptyBlock:\n  Enabled: true\nLint/EmptyClass:\n  Enabled: true\nLint/NoReturnInBeginEndBlocks:\n  Enabled: true\nLint/ToEnumArguments:\n  Enabled: true\nLint/UnmodifiedReduceAccumulator:\n  Enabled: true\nLint/UnexpectedBlockArity:\n  Enabled: true\nLint/DeprecatedConstants:\n  Enabled: true\nLint/LambdaWithoutLiteralBlock:\n  Enabled: true\nLint/NumberedParameterAssignment:\n  Enabled: true\nLint/OrAssignmentToConstant:\n  Enabled: true\nLint/RedundantDirGlobSort:\n  Enabled: true\nLint/SymbolConversion:\n  Enabled: true\nLint/TripleQuotes:\n  Enabled: true\nLint/AmbiguousAssignment:\n  Enabled: true\nLint/EmptyInPattern:\n  Enabled: true\nLint/AmbiguousOperatorPrecedence: # new in 1.21\n  Enabled: true\nLint/AmbiguousRange: # new in 1.19\n  Enabled: true\nLint/ConstantOverwrittenInRescue: # new in 1.31\n  Enabled: true\nLint/DuplicateMagicComment: # new in 1.37\n  Enabled: true\nLint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21\n  Enabled: true\nLint/NonAtomicFileOperation: # new in 1.31\n  Enabled: true\nLint/RefinementImportMethods: # new in 1.27\n  Enabled: true\nLint/RequireRangeParentheses: # new in 1.32\n  Enabled: true\nLint/RequireRelativeSelfPath: # new in 1.22\n  Enabled: true\nLint/UselessRuby2Keywords: # new in 1.23\n  Enabled: true\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# 2.1.1\n* Fix logging with symbolized keys JSON\n* Unknown formats return raw `Response::ResultSelt` like regular JSON query\n* Added methods `statistics`, `summary`, `headers` and `types` to `Response::ResultSet`\n\n# 2.1.0\n* `ClickHouse.connection.insert` now returns `ClickHouse::Response::Summary` object\n  with methods `headers`, `summary`, `written_rows`, `written_bytes`, etc... \n* `ClickHouse.connection.insert(columns: [\"id\"], values: [1])` now uses `JSONCompactEachRow` by default\n  (to increase JSON serialization speed)\n* Methods `insert_rows` and `insert_compact` added to `connection`\n* Added ability to pass object directly to insert like:\n  `ClickHouse.connection.insert(\"table\", {id: 1})` or\n  `ClickHouse.connection.insert(\"table\", [{id: 1})]` (for ruby < 3.0 use `ClickHouse.connection.insert(\"table\", [{id: 1}], {})`)\n* 🔥 Added config option `json_serializer` (one of `ClickHouse::Serializer::JsonSerializer`, `ClickHouse::Serializer::JsonOjSerializer`)\n* 🔥 Added config option `symbolize_keys`\n* 🔥 Added type serialization for INSERT statements, example below:\n\n```sql\nCREATE TABLE assets(visible Boolean, tags Array(Nullable(String))) ENGINE Memory\n```\n\n```ruby\n# cache table schema in a class variable\n@schema = ClickHouse.connection.table_schema('assets')\n\n# Json each row\nClickHouse.connection.insert('assets', @schema.serialize({'visible' => true, 'tags' => ['ruby']}))\n\n# Json compact\nClickHouse.connection.insert('assets', columns: %w[visible tags]) do |buffer|\n  buffer << [\n    @schema.serialize_column(\"visible\", true),\n    @schema.serialize_column(\"tags\", ['ruby']),\n  ]\nend\n```\n\n# 2.0.0\n* Fixed `Bigdecimal` casting with high precision\n* Added nested `type casting like Array(Array(Array(Nullable(T))))`\n* Added `Map(T1, T2)` support\n* Added `Tuple(T1, T2)` support\n* Added support for `Faraday` v1 and v2\n* Added support for `Oj` parser\n* Time types return `Time` class instead of `DateTime` for now\n\n# 1.6.3\n* [PR](https://github.com/shlima/click_house/pull/38) Add option format for insert\n* [PR](https://github.com/shlima/click_house/pull/34) Support X-ClickHouse-Exception-Code header\n* [ISSUE](https://github.com/shlima/click_house/issues/33) Fix parameterized types parsing\n* Added LowCardinality DDL support\n* Fixed body logging with POST queries\n\n# 1.6.2\n* [PR](https://github.com/shlima/click_house/pull/31) Add rows_before_limit_at_least to ResultSet\n* [PR](https://github.com/shlima/click_house/pull/29) Force JSON format by using \"default_format\" instead of modifying the query\n\n# 1.6.1\n* [PR](https://github.com/shlima/click_house/pull/26) call logging middleware when an error is raised\n\n# 1.6.0\n* [PR](https://github.com/shlima/click_house/pull/19) handle value returned as nil in float and integer types (case of Aggregate Function Combinators) \n* [PR](https://github.com/shlima/click_house/pull/18) Fix Faraday deprecation\n\n# 1.5.0\n* add support for 'WITH TOTALS' modifier in response\n* send SQL in GET request's body [#12](https://github.com/shlima/click_house/pull/12)\n* add support of 'WITH TOTALS' on a resulting set\n\n# 1.4.0\n* fix decimal type casting [#11](https://github.com/shlima/click_house/issues/11)\n\n# 1.3.9\n* add `ClickHouse.connection.add_index`, `ClickHouse.connection.drop_index`\n\n# 1.3.8\n* fix `DateTime` casting for queries like `ClickHouse.connection.select_value('select NOW()')` \n* fix resulting set console inspection\n\n# 1.3.7\n* specify required ruby version [#10](https://github.com/shlima/click_house/issues/10)\n\n# 1.3.6\n* fix ruby 2.7 warning `maybe ** should be added to the call` on `ClickHouse.connection.databases`\n\n# 1.3.5\n* added `ClickHouse.connection.explain(\"sql\")` \n\n# 1.3.4\n* added `ClickHouse.type_names(nullable: false)`\n* fixed `connection#create_table` column definitions\n* `ClickHouse.add_type` now handles Nullable types automatically\n\n# 1.3.3\n* fix logger typo\n\n# 1.3.2\n* fix null logger for windows users\n\n# 1.3.1\n* added request [headers](https://github.com/shlima/click_house/pull/8) support\n\n# 1.3.0\n* added support for IPv4/IPv6 types\n\n# 1.2.7\n* rubocop version bump\n\n# 1.2.6\n* Datetime64 field type support [#3](https://github.com/shlima/click_house/pull/3)\n"
  },
  {
    "path": "Gemfile",
    "content": "# frozen_string_literal: true\n\nsource 'https://rubygems.org'\n\ngit_source(:github) { |repo_name| \"https://github.com/#{repo_name}\" }\n\n# Specify your gem's dependencies in click_house.gemspec\ngemspec\n"
  },
  {
    "path": "Gemfile_faraday1",
    "content": "# frozen_string_literal: true\n\nsource 'https://rubygems.org'\n\ngit_source(:github) { |repo_name| \"https://github.com/#{repo_name}\" }\n\n# lock faraday to v1\ngem 'faraday', '< 2'\n\n# Specify your gem's dependencies in click_house.gemspec\ngemspec\n"
  },
  {
    "path": "LICENCE.txt",
    "content": "MIT License\n\nCOPYRIGHT (C) 2019 ALIAKSANDR SHYLAU\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: help\n\n.BUNDLE_GEMFILE:=\n.REQUIRE:=./spec/spec_helper\n\nhelp:\n\t@echo 'Available targets:'\n\t@echo '  make dockerize OR make ARGS=\"--build\" dockerize'\n\t@echo '  make release'\n\t@echo '  '\n\t@echo '  make faraday1 bundle'\n\t@echo '  make faraday2 bundle'\n\t@echo '  '\n\t@echo '  make faraday1 rspec'\n\t@echo '  make faraday2 rspec'\n\t@echo '  make faraday2 oj rspec'\n\ndockerize:\n\tdocker-compose up ${ARGS}\n\nrelease:\n\tbin/release.sh\n\nfaraday1:\n\t$(eval .BUNDLE_GEMFILE=Gemfile_faraday1)\n\nfaraday2:\n\t$(eval .BUNDLE_GEMFILE=Gemfile_faraday2)\n\noj:\n\t$(eval .REQUIRE=./spec/oj_helper)\n\nbundle:\n\tBUNDLE_GEMFILE=${.BUNDLE_GEMFILE} bundle\n\nrspec:\n\tBUNDLE_GEMFILE=${.BUNDLE_GEMFILE} rspec --require ${.REQUIRE} spec"
  },
  {
    "path": "README.md",
    "content": "![](./doc/logo.svg?sanitize=true)\n\n# ClickHouse Ruby driver\n\n![CI](https://github.com/shlima/click_house/workflows/CI/badge.svg)\n[![Code Climate](https://codeclimate.com/github/shlima/click_house/badges/gpa.svg)](https://codeclimate.com/github/shlima/click_house)\n[![Gem Version](https://badge.fury.io/rb/click_house.svg)](https://badge.fury.io/rb/click_house)\n\n```bash\ngem install click_house\n```\n\nA modern Ruby database driver for ClickHouse. [ClickHouse](https://clickhouse.yandex)\nis a high-performance column-oriented database management system developed by\n[Yandex](https://yandex.com/company) which operates Russia's most popular search engine.\n\n> This development was inspired by currently [unmaintainable alternative](https://github.com/archan937/clickhouse)\n> but rewritten and well tested\n\n### Why use the HTTP interface and not the TCP interface?\n\nWell, the developers of ClickHouse themselves [discourage](https://github.com/yandex/ClickHouse/issues/45#issuecomment-231194134) using the TCP interface.\n\n> TCP transport is more specific, we don't want to expose details.\nDespite we have full compatibility of protocol of different versions of client and server, we want to keep the ability to \"break\" it for very old clients. And that protocol is not too clean to make a specification.\n\nYandex uses HTTP interface for working from Java and Perl, Python and Go as well as shell scripts.\n\n# TOC\n\n* [Configuration](#configuration)\n* [Usage](#usage)\n* [Queries](#queries)\n* [Insert](#insert)\n* [Create a table](#create-a-table)\n* [Alter table](#alter-table)\n* [Type casting](#type-casting)\n* [Using with a connection pool](#using-with-a-connection-pool)\n* [Using with Rails](#using-with-rails)\n* [Using with ActiveRecord](#using-with-activerecord)\n* [Using with RSpec](#using-with-rspec)\n* [Development](#development)\n\n## Configuration\n\n```ruby\nClickHouse.config do |config|\n  config.logger = Logger.new(STDOUT)\n  config.adapter = :net_http\n  config.database = 'metrics'\n  config.url = 'http://localhost:8123'\n  config.timeout = 60\n  config.open_timeout = 3\n  config.ssl_verify = false\n  # set to true to symbolize keys for SELECT and INSERT statements (type casting)\n  config.symbolize_keys = false\n  config.headers = {}\n\n  # or provide connection options separately\n  config.scheme = 'http'\n  config.host = 'localhost'\n  config.port = 'port'\n\n  # if you use HTTP basic Auth\n  config.username = 'user'\n  config.password = 'password'\n\n  # if you want to add settings to all queries\n  config.global_params = { mutations_sync: 1 }\n  \n  # choose a ruby JSON parser (default one)\n  config.json_parser = ClickHouse::Middleware::ParseJson\n  # or Oj parser\n  config.json_parser = ClickHouse::Middleware::ParseJsonOj\n\n  # JSON.dump (default one)\n  config.json_serializer = ClickHouse::Serializer::JsonSerializer\n  # or Oj.dump\n  config.json_serializer = ClickHouse::Serializer::JsonOjSerializer\nend\n```\n\nAlternative, you can assign configuration parameters via a hash\n\n```ruby\nClickHouse.config.assign(logger: Logger.new(STDOUT))\n```\n\nNow you are able to communicate with ClickHouse:\n\n```ruby\nClickHouse.connection.ping #=> true\n```\nYou can easily build a new raw connection and override any configuration parameter\n(such as database name, connection address)\n\n```ruby\n@connection = ClickHouse::Connection.new(ClickHouse::Config.new(logger: Rails.logger))\n@connection.ping\n```\n\n## Usage\n\n```ruby\nClickHouse.connection.ping #=> true\nClickHouse.connection.replicas_status #=> true\n\nClickHouse.connection.databases #=> [\"default\", \"system\"]\nClickHouse.connection.create_database('metrics', if_not_exists: true, engine: nil, cluster: nil)\nClickHouse.connection.drop_database('metrics', if_exists: true, cluster: nil)\n\nClickHouse.connection.tables #=> [\"visits\"]\nClickHouse.connection.describe_table('visits') #=> [{\"name\"=>\"id\", \"type\"=>\"FixedString(16)\", \"default_type\"=>\"\"}]\nClickHouse.connection.table_exists?('visits', temporary: nil) #=> true\nClickHouse.connection.drop_table('visits', if_exists: true, temporary: nil, cluster: nil)\nClickHouse.connection.create_table(*) # see <Create a table> section\nClickHouse.connection.truncate_table('name', if_exists: true, cluster: nil)\nClickHouse.connection.truncate_tables(['table_1', 'table_2'], if_exists: true, cluster: nil)\nClickHouse.connection.truncate_tables # will truncate all tables in database\nClickHouse.connection.rename_table('old_name', 'new_name', cluster: nil)\nClickHouse.connection.rename_table(%w[table_1 table_2], %w[new_1 new_2], cluster: nil)\nClickHouse.connection.alter_table('table', 'DROP COLUMN user_id', cluster: nil)\nClickHouse.connection.add_index('table', 'ix', 'has(b, a)', type: 'minmax', granularity: 2, cluster: nil)\nClickHouse.connection.drop_index('table', 'ix', cluster: nil)\n\nClickHouse.connection.select_all('SELECT * FROM visits')\nClickHouse.connection.select_one('SELECT * FROM visits LIMIT 1')\nClickHouse.connection.select_value('SELECT ip FROM visits LIMIT 1')\nClickHouse.connection.explain('SELECT * FROM visits CROSS JOIN visits')\n```\n\n## Queries\n### Select All\n\nSelect all type-casted result set\n\n```ruby\n@result = ClickHouse.connection.select_all('SELECT * FROM visits')\n\n# all enumerable methods are delegated like #each, #map, #select etc\n# results of #to_a is TYPE CASTED\n@result.to_a #=> [{\"date\"=>#<Date: 2000-01-01>, \"id\"=>1}]\n\n# raw results (WITHOUT type casting)\n# much faster if selecting a large amount of data\n@result.data #=> [{\"date\"=>\"2000-01-01\", \"id\"=>1}, {\"date\"=>\"2000-01-02\", \"id\"=>2}]\n\n# you can access raw data\n@result.meta #=> [{\"name\"=>\"date\", \"type\"=>\"Date\"}, {\"name\"=>\"id\", \"type\"=>\"UInt32\"}]\n@result.statistics #=> {\"elapsed\"=>0.0002271, \"rows_read\"=>2, \"bytes_read\"=>12}\n@result.summary #=> ClickHouse::Response::Summary\n@result.headers #=> {\"x-clickhouse-query-id\"=>\"9bf5f604-31fc-4eff-a4b5-277f2c71d199\"}\n@result.types #=> [Hash<String|Symbol, ClickHouse::Ast::Statement>]\n```\n\n### Select Value\n\nSelect value returns exactly one type-casted value\n\n```ruby\nClickHouse.connection.select_value('SELECT COUNT(*) from visits') #=> 0\nClickHouse.connection.select_value(\"SELECT toDate('2019-01-01')\") #=> #<Date: 2019-01-01>\nClickHouse.connection.select_value(\"SELECT toDateOrZero(NULL)\") #=> nil\n```\n\n### Select One\n\nReturns a record hash with the column names as keys and column values as values.\n\n```ruby\nClickHouse.connection.select_one('SELECT date, SUM(id) AS sum FROM visits GROUP BY date')\n#=> {\"date\"=>#<Date: 2000-01-01>, \"sum\"=>1}\n```\n\n### Execute Raw SQL\n\nBy default, gem provides parser for `JSON` and `CSV` response formats. Type conversion\navailable for the `JSON`.\n\n```ruby\n# format not specified\nresponse = ClickHouse.connection.execute <<~SQL\n  SELECT count(*) AS counter FROM rspec\nSQL\n\nresponse.body #=> \"2\\n\"\n\n# JSON\nresponse = ClickHouse.connection.execute <<~SQL\n  SELECT count(*) AS counter FROM rspec FORMAT JSON\nSQL\n\nresponse.body #=> {\"meta\"=>[{\"name\"=>\"counter\", \"type\"=>\"UInt64\"}], \"data\"=>[{\"counter\"=>\"2\"}], \"rows\"=>1, \"statistics\"=>{\"elapsed\"=>0.0002412, \"rows_read\"=>2, \"bytes_read\"=>4}}\n\n# CSV\nresponse = ClickHouse.connection.execute <<~SQL\n  SELECT count(*) AS counter FROM rspec FORMAT CSV\nSQL\n\nresponse.body #=> [[\"2\"]]\n\n# You may use any format supported by ClickHouse\nresponse = ClickHouse.connection.execute <<~SQL\n  SELECT count(*) AS counter FROM rspec FORMAT RowBinary\nSQL\n\nresponse.body #=> \"\\u0002\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\"\n```\n\n## Insert\n\nWhen column names and values are transferred separately, data sends to the server \nusing `JSONCompactEachRow` format by default.\n\n```ruby\nClickHouse.connection.insert('table', columns: %i[id name]) do |buffer|\n  buffer << [1, 'Mercury']\n  buffer << [2, 'Venus']\nend\n\n# or\nClickHouse.connection.insert('table', columns: %i[id name], values: [[1, 'Mercury'], [2, 'Venus']])\n```\n\nWhen rows are passed as an Array or a Hash, data sends to the server\nusing `JSONEachRow` format by default.\n\n```ruby\nClickHouse.connection.insert('table', [{ name: 'Sun', id: 1 }, { name: 'Moon', id: 2 }])\n\n# or\nClickHouse.connection.insert('table', { name: 'Sun', id: 1 })\n\n# for ruby < 3.0 provide an extra argument\nClickHouse.connection.insert('table', { name: 'Sun', id: 1 }, {})\n\n# or\nClickHouse.connection.insert('table') do |buffer|\n  buffer << { name: 'Sun', id: 1 }\n  buffer << { name: 'Moon', id: 2 }\nend\n```\n\nSometimes it's needed to use other format than `JSONEachRow` For example if you want to send BigDecimal's \nyou could use `JSONStringsEachRow` format so string representation of `BigDecimal` will be parsed:\n\n```ruby\nClickHouse.connection.insert('table', { name: 'Sun', id: '1' }, format: 'JSONStringsEachRow')\n# or\nClickHouse.connection.insert_rows('table', { name: 'Sun', id: '1' }, format: 'JSONStringsEachRow')\n# or\nClickHouse.connection.insert_compact('table', columns: %w[name id], values: %w[Sun 1], format: 'JSONCompactStringsEachRow')\n```\n\nSee the [type casting](#type-casting) section to insert the data in a proper way.\n\n## Create a table\n### Create table using DSL\n\n```ruby\nClickHouse.connection.create_table('visits', if_not_exists: true, engine: 'MergeTree(date, (year, date), 8192)') do |t|\n  t.FixedString :id, 16\n  t.UInt16      :year, low_cardinality: true\n  t.Date        :date\n  t.DateTime    :time, 'UTC'\n  t.Decimal     :money, 5, 4\n  t.String      :event\n  t.UInt32      :user_id\n  t.IPv4        :ipv4\n  t.IPv6        :ipv6\nend\n```\n\n### Create nullable columns\n\n```ruby\nClickHouse.connection.create_table('visits', engine: 'TinyLog') do |t|\n  t.UInt16 :id, 16, nullable: true\nend\n```\n\n### Set column options\n\n```ruby\nClickHouse.connection.create_table('visits', engine: 'MergeTree(date, (year, date), 8192)') do |t|\n  t.UInt16  :year\n  t.Date    :date\n  t.UInt16  :id, 16, default: 0, ttl: 'date + INTERVAL 1 DAY'\nend\n```\n\n### Define column with custom SQL\n\n```ruby\nClickHouse.connection.create_table('visits', engine: 'TinyLog') do |t|\n  t << \"vendor Enum('microsoft' = 1, 'apple' = 2)\"\n  t << \"tags Array(String)\"\nend\n```\n\n### Define nested structures\n\n```ruby\nClickHouse.connection.create_table('visits', engine: 'TinyLog') do |t|\n  t.UInt8 :id\n  t.Nested :json do |n|\n    n.UInt8 :cid\n    n.Date  :created_at\n    n.Date  :updated_at\n  end\nend\n```\n\n### Set table options\n\n```ruby\nClickHouse.connection.create_table('visits',\n  order: 'year',\n  ttl: 'date + INTERVAL 1 DAY',\n  sample: 'year',\n  settings: 'index_granularity=8192',\n  primary_key: 'year',\n  engine: 'MergeTree') do |t|\n  t.UInt16  :year\n  t.Date    :date\nend\n```\n\n### Create table with raw SQL\n\n```ruby\nClickHouse.connection.execute <<~SQL\n  CREATE TABLE visits(int Nullable(Int8), date Nullable(Date)) ENGINE TinyLog\nSQL\n```\n\n## Alter table\n### Alter table with DSL\n```ruby\nClickHouse.connection.add_column('table', 'column_name', :UInt64, default: nil, if_not_exists: nil, after: nil, cluster: nil)\nClickHouse.connection.drop_column('table', 'column_name', if_exists: nil, cluster: nil)\nClickHouse.connection.clear_column('table', 'column_name', partition: 'partition_name', if_exists: nil, cluster: nil)\nClickHouse.connection.modify_column('table', 'column_name', type: :UInt64, default: nil, if_exists: false, cluster: nil)\n```\n\n### Alter table with SQL\n\n```ruby\n# By SQL in argument\nClickHouse.connection.alter_table('table', 'DROP COLUMN user_id', cluster: nil)\n\n# By SQL in a block\nClickHouse.connection.alter_table('table', cluster: nil) do\n  <<~SQL\n    MOVE PART '20190301_14343_16206_438' TO VOLUME 'slow'\n  SQL\nend\n```\n\n## Type casting\n\nBy default gem provides all necessary type casting, but you may overwrite or define\nyour own logic. if you need to redefine all built-in types with your implementation,\njust clear the default type system:\n\n```ruby\nClickHouse.types.clear\nClickHouse.types # => {}\nClickHouse.types.default #=> #<ClickHouse::Type::UndefinedType>\n```\n\nType casting works automatically when fetching data, when inserting data, you must serialize the types yourself\n\n```sql\nCREATE TABLE assets(visible Boolean, tags Array(Nullable(String))) ENGINE Memory\n```\n\n```ruby\n# cache table schema in a class variable\n@schema = ClickHouse.connection.table_schema('assets')\n\n# Json each row\nClickHouse.connection.insert('assets', @schema.serialize({'visible' => true, 'tags' => ['ruby']}))\n\n# Json compact\nClickHouse.connection.insert('assets', columns: %w[visible tags]) do |buffer|\n  buffer << [\n    @schema.serialize_column(\"visible\", true),\n    @schema.serialize_column(\"tags\", ['ruby']),\n  ]\nend\n```\n\n## Using with a connection pool\n\n```ruby\nrequire 'connection_pool'\n\nClickHouse.connection = ConnectionPool.new(size: 2) do\n  ClickHouse::Connection.new(ClickHouse::Config.new(url: 'http://replica.example.com'))\nend\n\nClickHouse.connection.with do |conn|\n  conn.tables\nend\n```\n\n## Using with Rails\n\n```yml\n# config/click_house.yml\n\ndefault: &default\n  url: http://localhost:8123\n  timeout: 60\n  open_timeout: 3\n\ndevelopment:\n  database: ecliptic_development\n  <<: *default\n\ntest:\n  database: ecliptic_test\n  <<: *default\n\nproduction:\n  <<: *default\n  database: ecliptic_production\n```\n\n```ruby\n# config/initializers/click_house.rb\n\nClickHouse.config do |config|\n  config.logger = Rails.logger\n  config.assign(Rails.application.config_for('click_house'))\nend\n```\n\n```ruby\n# lib/tasks/click_house.rake\nnamespace :click_house do\n  task prepare: :environment do\n    @environments = Rails.env.development? ? %w[development test] : [Rails.env]\n  end\n\n  task drop: :prepare do\n    @environments.each do |env|\n      config = ClickHouse.config.clone.assign(Rails.application.config_for('click_house', env: env))\n      connection = ClickHouse::Connection.new(config)\n      connection.drop_database(config.database, if_exists: true)\n    end\n  end\n\n  task create: :prepare do\n    @environments.each do |env|\n      config = ClickHouse.config.clone.assign(Rails.application.config_for('click_house', env: env))\n      connection = ClickHouse::Connection.new(config)\n      connection.create_database(config.database, if_not_exists: true)\n    end\n  end\nend\n```\n\nPrepare the ClickHouse database:\n\n```bash\nrake click_house:drop click_house:create\n```\n\nIf your are using SQL Database in Rails, you can manage ClickHouse migrations\nusing `ActiveRecord::Migration` mechanism\n\n```ruby\nclass CreateAdvertVisits < ActiveRecord::Migration[6.0]\n  def up\n    ClickHouse.connection.create_table('visits', engine: 'MergeTree(date, (account_id, advert_id), 512)') do |t|\n      t.UInt16   :account_id\n      t.UInt16   :user_id\n      t.Date     :date\n    end\n  end\n\n  def down\n    ClickHouse.connection.drop_table('visits')\n  end\nend\n```\n\n## Using with ActiveRecord\n\nif you use `ActiveRecord`, you can use the ORM query builder by using fake models\n(empty tables must be present in the SQL database `create_table :visits`)\n\n```ruby\nclass ClickHouseRecord < ActiveRecord::Base\n  self.abstract_class = true\n\n  class << self\n    def agent\n      ClickHouse.connection\n    end\n\n    def insert(*argv, &block)\n      agent.insert(table_name, *argv, &block)\n    end\n\n    def select_one\n      agent.select_one(current_scope.to_sql)\n    end\n\n    def select_value\n      agent.select_value(current_scope.to_sql)\n    end\n\n    def select_all\n      agent.select_all(current_scope.to_sql)\n    end\n\n    def explain\n      agent.explain(current_scope.to_sql)\n    end\n  end\nend\n````\n\n````ruby\n# FAKE MODEL FOR ClickHouse\nclass Visit < ClickHouseRecord\n  scope :with_os, -> { where.not(os_family_id: nil) }\nend\n\nVisit.with_os.select('COUNT(*) as counter').group(:ipv4).select_all\n#=> [{ 'ipv4' => 1455869, 'counter' => 104 }]\n\nVisit.with_os.select('COUNT(*)').select_value\n#=> 20_345_678\n\nVisit.where(user_id: 1).select_one\n#=> { 'ipv4' => 1455869, 'user_id' => 1 }\n````\n\n## Using with RSpec\n\nYou can clear the data table before each test with RSpec\n\n```ruby\nRSpec.configure do |config|\n  config.before(:each, truncate_click_house: true) do\n    ClickHouse.connection.truncate_tables\n  end\nend\n```\n\n```ruby\nRSpec.describe Api::MetricsCountroller, truncate_click_house: true do\n  it { }\n  it { }\nend\n```\n\n## Development\n\n```bash\nmake dockerize\nrspec\nrubocop\n```\n"
  },
  {
    "path": "Rakefile",
    "content": "# frozen_string_literal: true\n\nrequire 'bundler/gem_tasks'\nrequire 'rspec/core/rake_task'\n\nRSpec::Core::RakeTask.new(:spec)\n\ntask default: :spec\n"
  },
  {
    "path": "bin/console",
    "content": "#!/usr/bin/env ruby\n\nrequire 'bundler/setup'\nrequire 'click_house'\nrequire 'pry'\n\nClickHouse.config do |config|\n  config.logger = Logger.new(STDOUT)\nend\n\nPry.start\n"
  },
  {
    "path": "bin/release.sh",
    "content": "#!/usr/bin/env bash\n\nrm ./*.gem\ngem build click_house.gemspec\ngem push click_house-*\n"
  },
  {
    "path": "bin/setup",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\nIFS=$'\\n\\t'\nset -vx\n\nbundle install\n\n# Do any other automated setup that you need to do here\n"
  },
  {
    "path": "click_house.gemspec",
    "content": "lib = File.expand_path('../lib', __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequire 'click_house/version'\n\nGem::Specification.new do |spec|\n  spec.name          = 'click_house'\n  spec.version       = ClickHouse::VERSION\n  spec.authors       = ['Aliaksandr Shylau']\n  spec.email         = ['alex.shilov.by@gmail.com']\n  spec.summary       = 'Modern Ruby database driver for ClickHouse'\n  spec.description   = 'Yandex ClickHouse database interface for Ruby'\n  spec.homepage      = 'https://github.com/shlima/click_house'\n  spec.required_ruby_version = '>= 2.7.0'\n\n  # Specify which files should be added to the gem when it is released.\n  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.\n  spec.files         = Dir.chdir(File.expand_path('..', __FILE__)) do\n    `git ls-files -z`.split(\"\\x0\").reject { |f| f.match(%r{^(test|spec|features)/}) }\n  end\n  spec.require_paths = ['lib']\n\n  spec.add_dependency 'faraday', '>= 1.7', '< 3'\n  spec.add_dependency 'activesupport'\n  spec.add_development_dependency 'bundler'\n  spec.add_development_dependency 'rake'\n  spec.add_development_dependency 'oj'\n  spec.add_development_dependency 'rspec'\n  spec.add_development_dependency 'pry'\n  spec.add_development_dependency 'rubocop'\n  spec.add_development_dependency 'rubocop-performance'\nend\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: '3.5'\n\nservices:\n  clickhouse:\n    image: clickhouse/clickhouse-server:22.9\n    ports:\n      - \"8123:8123\"\n      - \"9000:9000\"\n      - \"9009:9009\"\n    ulimits:\n      nproc: 65535\n      nofile:\n        soft: 262144\n        hard: 262144\n    volumes:\n      - ./tmp/clickhouse-data:/opt/clickhouse/data\n    networks:\n      - default\n\nnetworks:\n  default:\n"
  },
  {
    "path": "lib/click_house/ast/parser.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'stringio'\n\nmodule ClickHouse\n  module Ast\n    class Parser\n      OPEN = '('\n      CLOSED = ')'\n      COMMA = ','\n      SPACE = ' '\n\n      attr_reader :input\n\n      # @param input [String]\n      def initialize(input)\n        @input = input\n      end\n\n      # @refs https://clickhouse.com/docs/en/sql-reference/data-types/\n      # Map(String, Decimal(10, 5))\n      # Array(Array(Array(Array(Nullable(Int, String)))))\n      def parse\n        ticker = Ticker.new\n        control = false\n\n        input.each_char do |char|\n          # cases like (1,<space after comma> 3)\n          next if control && char == SPACE\n\n          case char\n          when OPEN\n            control = true\n            ticker.open\n          when CLOSED\n            control = true\n            ticker.close\n          when COMMA\n            control = true\n            ticker.comma\n          else\n            control = false\n            ticker.char(char)\n          end\n        end\n\n        # if a single type like \"Int\"\n        ticker.current.name! unless control\n        ticker.current\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/ast/statement.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'stringio'\n\nmodule ClickHouse\n  module Ast\n    class Statement\n      PLACEHOLDER_S = '%s'\n      PLACEHOLDER_D = '%d'\n      DIGIT_RE = /\\A\\d+\\Z/.freeze\n\n      attr_reader :name\n      attr_accessor :caster\n\n      def initialize(name: '')\n        @buffer = ''\n        @name = name\n      end\n\n      # @param value [String]\n      def print(value)\n        @buffer = \"#{@buffer}#{value}\"\n      end\n\n      def name!\n        @name = @buffer\n        @buffer = ''\n      end\n\n      def argument!\n        add_argument(Statement.new(name: @buffer))\n        @buffer = ''\n      end\n\n      # @param st [Statement]\n      def add_argument(st)\n        arguments.push(st)\n      end\n\n      # @param other [Statement]\n      def merge(other)\n        if other.named?\n          add_argument(other)\n        else\n          @arguments = arguments.concat(other.arguments)\n        end\n      end\n\n      def named?\n        !@name.empty?\n      end\n\n      def buffer?\n        !@buffer.empty?\n      end\n\n      # @return [Array<Statement>]\n      def arguments\n        @arguments ||= []\n      end\n\n      # @return [Array]\n      # cached argument values to increase the casting perfomance\n      def argument_values\n        @argument_values ||= arguments.map(&:value)\n      end\n\n      def argument_first!\n        # TODO: raise an error if multiple arguments\n        @argument_first ||= arguments.first\n      end\n\n      def placeholder\n        return @placeholder if defined?(@placeholder)\n\n        @placeholder = digit? ? PLACEHOLDER_D : PLACEHOLDER_S\n      end\n\n      def digit?\n        name.match?(DIGIT_RE)\n      end\n\n      def value\n        @value ||=\n          case placeholder\n          when PLACEHOLDER_D\n            Integer(name)\n          when PLACEHOLDER_S\n            # remove leading and trailing quotes\n            name[1..-2]\n          else\n            raise \"unknown value extractor for <#{placeholder}>\"\n          end\n      end\n\n      def to_s\n        out = StringIO.new\n        out.print(name.empty? ? 'NO_NAME' : name)\n        out.print(\"<#{@buffer}>\") unless @buffer.empty?\n\n        if arguments.any?\n          out.print(\"(#{arguments.join(',')})\")\n        end\n\n        out.string\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/ast/ticker.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'stringio'\n\nmodule ClickHouse\n  module Ast\n    class Ticker\n      attr_reader :root, :current\n\n      def initialize\n        @current = Statement.new\n      end\n\n      def open\n        current.name!\n        opened.push(current)\n        @current = Statement.new\n      end\n\n      def comma\n        current.argument! if current.buffer?\n        opened.last.merge(current)\n        @current = Statement.new\n      end\n\n      def close\n        current.argument! unless current.named?\n        opened.last.merge(current)\n        @current = opened.pop\n      end\n\n      # @param char [String]\n      def char(char)\n        current.print(char)\n      end\n\n      def opened\n        @opened ||= []\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/ast.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Ast\n    autoload :Statement, 'click_house/ast/statement'\n    autoload :Ticker, 'click_house/ast/ticker'\n    autoload :Parser, 'click_house/ast/parser'\n  end\nend\n"
  },
  {
    "path": "lib/click_house/benchmark/casting.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'bundler/setup'\nrequire 'benchmark'\nrequire 'pry'\nrequire_relative '../../click_house'\n\n\nClickHouse.config.json_serializer = ClickHouse::Serializer::JsonOjSerializer\nClickHouse.config.json_parser = ClickHouse::Middleware::ParseJsonOj\nClickHouse.connection.drop_table('benchmark', if_exists: true)\nClickHouse.connection.execute <<~SQL\nCREATE TABLE benchmark(\n  int   Nullable(Int8),\n  date  Nullable(Date),\n  array Array(String),\n  map   Map(String, IPv4)\n) ENGINE Memory\nSQL\n\nINPUT = Array.new(200_000, {\n  'int' => 21341234,\n  'date' => Date.new(2022, 1, 1),\n  'array' => ['foo'],\n  'map' => {'ip' => IPAddr.new('127.0.0.1')}\n})\n\nBenchmark.bm do |x|\n  x.report('insert: no casting') do\n    ClickHouse.connection.insert('benchmark', INPUT)\n  end\n\n  x.report('insert: with casting') do\n    schema = ClickHouse.connection.table_schema('benchmark')\n    ClickHouse.connection.insert('benchmark', schema.serialize(INPUT))\n  end\n\n  x.report('select: no casting') do\n    ClickHouse.connection.select_all('SELECT * FROM benchmark').data\n  end\n\n  x.report('select: with casting') do\n    ClickHouse.connection.select_all('SELECT * FROM benchmark').to_a\n  end\nend\n"
  },
  {
    "path": "lib/click_house/benchmark/map_join.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'benchmark'\nrequire 'stringio'\n\nINPUT = Array.new(5_000_000, 'foo bar')\n\nBenchmark.bm do |x|\n  x.report('map.join') do\n    INPUT.map(&:to_s).join(\"\\n\")\n  end\n\n  x.report('StringIO') do\n    out = StringIO.new\n    INPUT.each do |value|\n      out << \"#{value}\\n\"\n    end\n    out.string\n  end\nend\n"
  },
  {
    "path": "lib/click_house/config.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  class Config\n    DEFAULTS = {\n      adapter: Faraday.default_adapter,\n      url: nil,\n      scheme: 'http',\n      host: 'localhost',\n      port: '8123',\n      logger: nil,\n      database: nil,\n      username: nil,\n      password: nil,\n      timeout: nil,\n      open_timeout: nil,\n      ssl_verify: false,\n      headers: {},\n      global_params: {},\n      json_parser: ClickHouse::Middleware::ParseJson,\n      json_serializer: ClickHouse::Serializer::JsonSerializer,\n      oj_dump_options: {\n        mode: :compat # to be able to dump improper JSON like {1 => 2}\n      },\n      oj_load_options: {\n        mode: :custom,\n        allow_blank: true,\n        bigdecimal_as_decimal: false, # dump BigDecimal as a String\n        bigdecimal_load: :bigdecimal, # convert all decimal numbers to BigDecimal\n      },\n      json_load_options: {\n        decimal_class: BigDecimal,\n      },\n      # should be after json load options\n      symbolize_keys: false,\n    }.freeze\n\n    attr_accessor :adapter\n    attr_accessor :logger\n    attr_accessor :scheme\n    attr_accessor :host\n    attr_accessor :port\n    attr_accessor :database\n    attr_accessor :url\n    attr_accessor :username\n    attr_accessor :password\n    attr_accessor :timeout\n    attr_accessor :open_timeout\n    attr_accessor :ssl_verify\n    attr_accessor :headers\n    attr_accessor :global_params\n    attr_accessor :oj_load_options\n    attr_accessor :json_load_options\n    attr_accessor :json_parser # response middleware\n    attr_accessor :oj_dump_options\n    attr_accessor :json_serializer # [ClickHouse::Serializer::Base]\n    attr_accessor :symbolize_keys # [NilClass, Boolean]\n\n    def initialize(params = {})\n      assign(DEFAULTS.merge(params))\n      yield(self) if block_given?\n    end\n\n    # @return [self]\n    def assign(params = {})\n      params.each { |k, v| public_send(\"#{k}=\", v) }\n\n      self\n    end\n\n    def auth?\n      !username.nil? || !password.nil?\n    end\n\n    def logger!\n      @logger || null_logger\n    end\n\n    def url!\n      @url || \"#{scheme}://#{host}:#{port}\"\n    end\n\n    def null_logger\n      @null_logger ||= Logger.new(IO::NULL)\n    end\n\n    # @param klass [ClickHouse::Serializer::Base]\n    def json_serializer=(klass)\n      @json_serializer = klass.new(self)\n    end\n\n    def symbolize_keys=(value)\n      bool = value ? true : false\n\n      # merge to be able to clone a config\n      # prevent overriding default values\n      self.oj_load_options = oj_load_options.merge(symbol_keys: bool)\n      self.json_load_options = json_load_options.merge(symbolize_names: bool)\n      @symbolize_keys = bool\n    end\n\n    # @param name [Symbol, String]\n    def key(name)\n      symbolize_keys ? name.to_sym : name.to_s\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/connection.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  class Connection\n    include Extend::ConnectionHealthy\n    include Extend::ConnectionDatabase\n    include Extend::ConnectionTable\n    include Extend::ConnectionSelective\n    include Extend::ConnectionInserting\n    include Extend::ConnectionAltering\n    include Extend::ConnectionExplaining\n\n    attr_reader :config\n\n    # @param [Config]\n    def initialize(config)\n      @config = config\n    end\n\n    def execute(query, body = nil, database: config.database, params: {})\n      post(body, query: { query: query }, database: database, params: config.global_params.merge(params))\n    end\n\n    # @param path [String] Clickhouse HTTP endpoint, e.g. /ping, /replica_status\n    # @param body [String] SQL to run\n    # @param database [String|NilClass] database to use, nil to skip\n    # @param query [Hash] other CH settings to send through params, e.g. max_rows_to_read=1\n    # @example get(body: 'select number from system.numbers limit 100', query: { max_rows_to_read: 10 })\n    # @return [Faraday::Response]\n    def get(path = '/', body: '', query: {}, database: config.database)\n      # backward compatibility since\n      # https://github.com/shlima/click_house/pull/12/files#diff-9c6f3f06d3b575731eae4b6b95ddbcdcc20452c432b8f6e87a3a8e8645818107R24\n      if query.is_a?(String)\n        query = { query: query }\n        config.logger!.warn('since v1.4.0 use connection.get(body: \"SELECT 1\") instead of connection.get(query: \"SELECT 1\")')\n      end\n\n      transport.get(path) do |conn|\n        conn.params = query.merge(database: database).compact\n        conn.params[:send_progress_in_http_headers] = 1 unless body.empty?\n        conn.body = body\n      end\n    end\n\n    def post(body = nil, query: {}, database: config.database, params: {})\n      transport.post(compose('/', query.merge(database: database, **params)), body)\n    end\n\n    # transport should work the same both with Faraday v1 and Faraday v2\n    # rubocop:disable Metrics/AbcSize\n    def transport\n      @transport ||= Faraday.new(config.url!) do |conn|\n        conn.options.timeout = config.timeout\n        conn.options.open_timeout = config.open_timeout\n        conn.headers = config.headers\n        conn.ssl.verify = config.ssl_verify\n\n        if config.auth?\n          if faraday_v1?\n            conn.request :basic_auth, config.username, config.password\n          else\n            conn.request :authorization, :basic, config.username, config.password\n          end\n        end\n\n        conn.response Middleware::RaiseError\n        conn.response Middleware::Logging, logger: config.logger!\n        conn.response Middleware::SummaryMiddleware, options: { config: config } # should be after logger\n        conn.response config.json_parser, content_type: %r{application/json}, options: { config: config }\n        conn.response Middleware::ParseCsv, content_type: %r{text/csv}, options: { config: config }\n        conn.adapter config.adapter\n      end\n    end\n    # rubocop:enable Metrics/AbcSize\n\n    def compose(path, query = {})\n      # without <query.compact> \"DB::Exception: Empty query\" error will occur\n      \"#{path}?#{URI.encode_www_form({ send_progress_in_http_headers: 1 }.merge(query).compact)}\"\n    end\n\n    # @return [Boolean]\n    def faraday_v1?\n      Faraday::VERSION.start_with?('1')\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/definition/column.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Definition\n    class Column\n      attr_accessor :name\n      attr_accessor :type\n      attr_accessor :nullable\n      attr_accessor :low_cardinality\n      attr_accessor :extensions\n      attr_accessor :default\n      attr_accessor :materialized\n      attr_accessor :ttl\n\n      def initialize(params = {})\n        params.each { |k, v| public_send(\"#{k}=\", v) }\n        yield(self) if block_given?\n      end\n\n      def to_s\n        type = extension_type\n        type = \"Nullable(#{type})\" if nullable\n        type = \"LowCardinality(#{type})\" if low_cardinality\n\n        \"#{name} #{type} #{opts}\"\n      end\n\n      def opts\n        options = {\n          DEFAULT: Util::Statement.ensure(default, default),\n          MATERIALIZED: Util::Statement.ensure(materialized, materialized),\n          TTL: Util::Statement.ensure(ttl, ttl)\n        }.compact\n\n        result = options.each_with_object([]) do |(key, value), object|\n          object << \"#{key} #{value}\"\n        end\n\n        result.join(' ')\n      end\n\n      def extension_type\n        extensions.nil? ? type : format(type, *extensions)\n      rescue TypeError, ArgumentError\n        raise StandardError, \"please provide extensions for <#{type}>\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/definition/column_set.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Definition\n    class ColumnSet\n      TYPES = ClickHouse.types.each_with_object([]) do |(name, type), object|\n        object << name.sub('%s', \"'%s'\") if type.ddl?\n      end\n\n      class << self\n        # @input \"DateTime('%s')\"\n        # @output \"DateTime\"\n        def method_name_for_type(type)\n          type.sub(/\\(.+/, '')\n        end\n      end\n\n      TYPES.each do |type|\n        method_name = method_name_for_type(type)\n        # t.Decimal :customer_id, nullable: true, default: ''\n        # t.Decimal :money, 1, 2, nullable: true, default: ''\n        class_eval <<-METHODS, __FILE__, __LINE__ + 1\n          def #{method_name}(*definition)\n            name = definition[0]\n            extentions = []\n            options = {}\n            extensions = Array(definition[1..-1]).each do |el|\n              el.is_a?(Hash) ? options.merge!(el) : extentions.push(el)\n            end\n\n            columns << Column.new(type: \"#{type}\", name: name, extensions: extensions, **options)\n          end\n        METHODS\n      end\n\n      def initialize\n        yield(self) if block_given?\n      end\n\n      def columns\n        @columns ||= []\n      end\n\n      def to_s\n        <<~SQL\n          ( #{columns.map(&:to_s).join(', ')} )\n        SQL\n      end\n\n      # @example\n      #   t.Nested :json do |n|\n      #     n.UInt8 :city_id\n      #   end\n      def nested(name, &block)\n        columns << \"#{name} Nested #{ColumnSet.new(&block)}\"\n      end\n\n      alias_method :Nested, :nested\n\n      def push(sql)\n        columns << sql\n      end\n\n      alias_method :<<, :push\n    end\n  end\nend\n\n__END__\n\ndata = ClickHouse::Definition::ColumnSet.new do |t|\n  t << \"words Enum('hello' = 1, 'world' = 2)\"\nend\n\nputs data.to_s\n\ndata = ClickHouse::Definition::ColumnSet.new do |t|\n  t.Decimal :money\n  t.Float32 :client_id, default: 0\n  t.Float32 :city_id, default: 0, nullable: true\n  t.Nested :json do |n|\n    n.Date :created_at\n    n.Date :updated_at\n  end\n\n  t << \"CUSTOM SQL\"\nend\n"
  },
  {
    "path": "lib/click_house/definition.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Definition\n    autoload :Column, 'click_house/definition/column'\n    autoload :ColumnSet, 'click_house/definition/column_set'\n  end\nend\n"
  },
  {
    "path": "lib/click_house/errors.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  Error = Class.new(StandardError)\n  NetworkException = Class.new(Error)\n  DbException = Class.new(Error)\n  StatementException = Class.new(Error)\n  SerializeError = Class.new(Error)\nend\n"
  },
  {
    "path": "lib/click_house/extend/configurable.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module Configurable\n      def config(&block)\n        yield(@config) if defined?(@config) && block\n\n        @config ||= Config.new(&block)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/extend/connectible.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module Connectible\n      def connection=(connection)\n        @connection = connection\n      end\n\n      def connection\n        @connection ||= Connection.new(config.clone)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/extend/connection_altering.rb",
    "content": "# frozen_string_literal: true\n\n# rubocop:disable Metrics/ParameterLists\nmodule ClickHouse\n  module Extend\n    module ConnectionAltering\n      def add_column(table, name, type, default: nil, if_not_exists: false, after: nil, cluster: nil)\n        sql = 'ADD COLUMN %<exists>s %<name>s %<type>s %<default>s %<after>s'\n\n        pattern = {\n          name: name,\n          exists: Util::Statement.ensure(if_not_exists, 'IF NOT EXISTS'),\n          type: type,\n          default: Util::Statement.ensure(default, \"DEFAULT #{default}\"),\n          after: Util::Statement.ensure(after, \"AFTER #{after}\")\n        }\n\n        alter_table(table, format(sql, pattern), cluster: cluster)\n      end\n\n      def drop_column(table, name, if_exists: false, cluster: nil)\n        sql = 'DROP COLUMN %<exists>s %<name>s'\n\n        pattern = {\n          name: name,\n          exists: Util::Statement.ensure(if_exists, 'IF EXISTS')\n        }\n\n        alter_table(table, format(sql, pattern), cluster: cluster)\n      end\n\n      def clear_column(table, name, partition:, if_exists: false, cluster: nil)\n        sql = 'CLEAR COLUMN %<exists>s %<name>s %<partition>s'\n\n        pattern = {\n          name: name,\n          exists: Util::Statement.ensure(if_exists, 'IF EXISTS'),\n          partition: \"IN PARTITION #{partition}\"\n        }\n\n        alter_table(table, format(sql, pattern), cluster: cluster)\n      end\n\n      def modify_column(table, name, type: nil, default: nil, if_exists: false, cluster: nil)\n        sql = 'MODIFY COLUMN %<exists>s %<name>s %<type>s %<default>s'\n\n        pattern = {\n          name: name,\n          type: type,\n          exists: Util::Statement.ensure(if_exists, 'IF EXISTS'),\n          default: Util::Statement.ensure(default, \"DEFAULT #{default}\")\n        }\n\n        alter_table(table, format(sql, pattern), cluster: cluster)\n      end\n\n      def alter_table(name, sql = nil, cluster: nil)\n        template = 'ALTER TABLE %<name>s %<cluster>s %<sql>s'\n        sql = yield(sql) if sql.nil?\n\n        pattern = {\n          name: name,\n          sql: sql,\n          cluster: Util::Statement.ensure(cluster, \"ON CLUSTER #{cluster}\"),\n        }\n\n        execute(format(template, pattern)).success?\n      end\n\n      def add_index(\n        table_name,\n        name,\n        expression,\n        type:,\n        granularity: nil,\n        after: nil,\n        cluster: nil\n      )\n        template = 'ADD INDEX %<name>s %<expression>s TYPE %<type>s GRANULARITY %<granularity>d %<after>s'\n        pattern = {\n          name: name,\n          expression: expression,\n          type: type,\n          granularity: granularity,\n          after: Util::Statement.ensure(after, \"AFTER #{after}\"),\n        }\n\n        alter_table(table_name, format(template, pattern), cluster: cluster)\n      end\n\n      def drop_index(table_name, name, cluster: nil)\n        alter_table(table_name, <<~SQL, cluster: cluster)\n          DROP INDEX #{name}\n        SQL\n      end\n    end\n  end\nend\n# rubocop:enable Metrics/ParameterLists\n"
  },
  {
    "path": "lib/click_house/extend/connection_database.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module ConnectionDatabase\n      # @return [Array<String>]\n      def databases\n        Array(execute('SHOW DATABASES FORMAT CSV', database: nil).body).tap(&:flatten!)\n      end\n\n      def create_database(name, if_not_exists: false, cluster: nil, engine: nil)\n        sql = 'CREATE DATABASE %<exists>s %<name>s %<cluster>s %<engine>s'\n\n        pattern = {\n          name: name,\n          exists: Util::Statement.ensure(if_not_exists, 'IF NOT EXISTS'),\n          cluster: Util::Statement.ensure(cluster, \"ON CLUSTER #{cluster}\"),\n          engine: Util::Statement.ensure(engine, \"ENGINE = #{engine}\")\n        }\n\n        execute(format(sql, pattern), database: nil).success?\n      end\n\n      def drop_database(name, if_exists: false, cluster: nil)\n        sql = 'DROP DATABASE %<exists>s %<name>s %<cluster>s'\n\n        pattern = {\n          name: name,\n          exists: Util::Statement.ensure(if_exists, 'IF EXISTS'),\n          cluster: Util::Statement.ensure(cluster, \"ON CLUSTER #{cluster}\"),\n        }\n\n        execute(format(sql, pattern), database: nil).success?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/extend/connection_explaining.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module ConnectionExplaining\n      EXPLAIN = 'EXPLAIN'\n      EXPLAIN_RE = /\\A(\\s*#{EXPLAIN})/io.freeze\n\n      # @return String\n      def explain(sql, io: StringIO.new)\n        res = execute(\"#{EXPLAIN} #{sql.gsub(EXPLAIN_RE, '')}\")\n        io.puts(res.body)\n        io.string\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/extend/connection_healthy.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module ConnectionHealthy\n      def ping\n        get('/ping', database: nil).success?\n      end\n\n      def replicas_status\n        get('/replicas_status', database: nil).success?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/extend/connection_inserting.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module ConnectionInserting\n      DEFAULT_JSON_EACH_ROW_FORMAT = 'JSONEachRow'\n      DEFAULT_JSON_COMPACT_EACH_ROW_FORMAT = 'JSONCompactEachRow'\n\n      # @return [Boolean]\n      #\n      # == Example with a block\n      # insert('rspec', columns: %i[name id]) do |buffer|\n      #   buffer << ['Sun', 1]\n      #   buffer << ['Moon', 2]\n      # end\n      #\n      # @return [Response::Execution]\n      # @param body [Array, Hash]\n      # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity\n      def insert(table, body = [], **opts)\n        # In Ruby < 3.0, if the last argument is a hash, and the method being called\n        # accepts keyword arguments, then it is always converted to keyword arguments.\n        columns = opts.fetch(:columns, [])\n        values =  opts.fetch(:values, [])\n        format = opts.fetch(:format, nil)\n\n        yield(body) if block_given?\n\n        # values: [{id: 1}]\n        if values.any? && columns.empty?\n          return insert_rows(table, values, format: format)\n        end\n\n        # body: [{id: 1}]\n        if body.any? && columns.empty?\n          return insert_rows(table, body, format: format)\n        end\n\n        # body: [1], columns: [\"id\"]\n        if body.any? && columns.any?\n          return insert_compact(table, columns: columns, values: body, format: format)\n        end\n\n        # columns: [\"id\"], values: [[1]]\n        if columns.any? && values.any?\n          return insert_compact(table, columns: columns, values: values, format: format)\n        end\n\n        Response::Factory.empty_exec(config)\n      end\n      # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity\n\n      # @param table [String]\n      # @param body [Array, Hash]\n      # @param format [String]\n      # @return [Response::Execution]\n      #\n      # Sometimes it's needed to use other format than JSONEachRow\n      # For example if you want to send BigDecimal's you could use\n      # JSONStringsEachRow format so string representation of BigDecimal will be parsed\n      def insert_rows(table, body, format: nil)\n        format ||= DEFAULT_JSON_EACH_ROW_FORMAT\n\n        case body\n        when Hash\n          Response::Factory.exec(execute(\"INSERT INTO #{table} FORMAT #{format}\", config.json_serializer.dump(body)))\n        when Array\n          Response::Factory.exec(execute(\"INSERT INTO #{table} FORMAT #{format}\", config.json_serializer.dump_each_row(body)))\n        else\n          raise ArgumentError, \"unknown body class <#{body.class}>\"\n        end\n      end\n\n      # @return [Response::Execution]\n      def insert_compact(table, columns: [], values: [], format: nil)\n        format ||= DEFAULT_JSON_COMPACT_EACH_ROW_FORMAT\n\n        yield(values) if block_given?\n\n        response = execute(\"INSERT INTO #{table} (#{columns.join(',')}) FORMAT #{format}\", config.json_serializer.dump_each_row(values))\n        Response::Factory.exec(response)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/extend/connection_selective.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module ConnectionSelective\n      # @return [ResultSet]\n      def select_all(sql)\n        response = get(body: sql, query: { default_format: 'JSON' })\n        Response::Factory.response(response, config)\n      end\n\n      def select_value(sql)\n        response = get(body: sql, query: { default_format: 'JSON' })\n        got = Response::Factory.response(response, config).first\n\n        case got\n        when Hash\n          Array(got).dig(0, -1) # get a value of a first key for JSON format\n        when Array\n          got[0] # for CSV format\n        else\n          got # for RowBinary format\n        end\n      end\n\n      def select_one(sql)\n        response = get(body: sql, query: { default_format: 'JSON' })\n        Response::Factory.response(response, config).first\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/extend/connection_table.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module ConnectionTable\n      # @return [Array<String>]\n      def tables\n        Array(execute('SHOW TABLES FORMAT CSV').body).tap(&:flatten!)\n      end\n\n      # @return [ResultSet]\n      def describe_table(name)\n        Response::Factory.response(execute(\"DESCRIBE TABLE #{name} FORMAT JSON\"), config)\n      end\n\n      # @return [ResultSet]\n      def table_schema(name)\n        Response::Factory.response(execute(\"SELECT * FROM #{name} WHERE 1=0 FORMAT JSON\"), config)\n      end\n\n      # @return [Boolean]\n      def table_exists?(name, temporary: false)\n        sql = 'EXISTS %<temporary>s TABLE  %<name>s FORMAT CSV'\n\n        pattern = {\n          name: name,\n          temporary: Util::Statement.ensure(temporary, 'TEMPORARY')\n        }\n\n        Type::BooleanType.new.cast(execute(format(sql, pattern)).body.dig(0, 0))\n      end\n\n      def drop_table(name, temporary: false, if_exists: false, cluster: nil)\n        sql = 'DROP %<temporary>s TABLE %<exists>s %<name>s %<cluster>s'\n\n        pattern = {\n          name: name,\n          temporary: Util::Statement.ensure(temporary, 'TEMPORARY'),\n          exists: Util::Statement.ensure(if_exists, 'IF EXISTS'),\n          cluster: Util::Statement.ensure(cluster, \"ON CLUSTER #{cluster}\"),\n        }\n\n        execute(format(sql, pattern)).success?\n      end\n\n      # rubocop:disable Metrics/ParameterLists\n      def create_table(\n        name,\n        if_not_exists: false, cluster: nil,\n        partition: nil, order: nil, primary_key: nil, sample: nil, ttl: nil, settings: nil,\n        engine:,\n        &block\n      )\n        sql = <<~SQL\n          CREATE TABLE %<exists>s %<name>s %<cluster>s %<definition>s %<engine>s\n            %<partition>s\n            %<order>s\n            %<primary_key>s\n            %<sample>s\n            %<ttl>s\n            %<settings>s\n        SQL\n        definition = ClickHouse::Definition::ColumnSet.new(&block)\n\n        pattern = {\n          name: name,\n          exists: Util::Statement.ensure(if_not_exists, 'IF NOT EXISTS'),\n          definition: definition.to_s,\n          cluster: Util::Statement.ensure(cluster, \"ON CLUSTER #{cluster}\"),\n          partition: Util::Statement.ensure(partition, \"PARTITION BY #{partition}\"),\n          order: Util::Statement.ensure(order, \"ORDER BY #{order}\"),\n          primary_key: Util::Statement.ensure(primary_key, \"PRIMARY KEY #{primary_key}\"),\n          sample: Util::Statement.ensure(sample, \"SAMPLE BY  #{sample}\"),\n          ttl: Util::Statement.ensure(ttl, \"TTL #{ttl}\"),\n          settings: Util::Statement.ensure(settings, \"SETTINGS #{settings}\"),\n          engine: Util::Statement.ensure(engine, \"ENGINE = #{engine}\")\n        }\n\n        execute(format(sql, pattern)).success?\n      end\n      # rubocop:enable Metrics/ParameterLists\n\n      def truncate_table(name, if_exists: false, cluster: nil)\n        sql = 'TRUNCATE TABLE %<exists>s %<name>s %<cluster>s'\n\n        pattern = {\n          name: name,\n          exists: Util::Statement.ensure(if_exists, 'IF EXISTS'),\n          cluster: Util::Statement.ensure(cluster, \"ON CLUSTER #{cluster}\")\n        }\n\n        execute(format(sql, pattern)).success?\n      end\n\n      def truncate_tables(names = tables, *argv)\n        Array(names).each { |name| truncate_table(name, *argv) }\n      end\n\n      def rename_table(from, to, cluster: nil)\n        from = Array(from)\n        to = Array(to)\n\n        unless from.length == to.length\n          raise StatementException, '<from> tables length should equal <to> length'\n        end\n\n        sql = <<~SQL\n          RENAME TABLE %<names>s %<cluster>s\n        SQL\n\n        pattern = {\n          names: from.zip(to).map { |a| a.join(' TO ') }.join(', '),\n          cluster: Util::Statement.ensure(cluster, \"ON CLUSTER #{cluster}\")\n        }\n\n        execute(format(sql, pattern)).success?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/extend/type_definition.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module TypeDefinition\n      def types\n        @types ||= Hash.new(Type::UndefinedType.new)\n      end\n\n      def add_type(type, klass)\n        types[type] = klass\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/extend.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    autoload :TypeDefinition, 'click_house/extend/type_definition'\n    autoload :Configurable, 'click_house/extend/configurable'\n    autoload :Connectible, 'click_house/extend/connectible'\n    autoload :ConnectionHealthy, 'click_house/extend/connection_healthy'\n    autoload :ConnectionDatabase, 'click_house/extend/connection_database'\n    autoload :ConnectionTable, 'click_house/extend/connection_table'\n    autoload :ConnectionSelective, 'click_house/extend/connection_selective'\n    autoload :ConnectionInserting, 'click_house/extend/connection_inserting'\n    autoload :ConnectionAltering, 'click_house/extend/connection_altering'\n    autoload :ConnectionExplaining, 'click_house/extend/connection_explaining'\n  end\nend\n"
  },
  {
    "path": "lib/click_house/middleware/logging.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    class Logging < Faraday::Middleware\n      Faraday::Response.register_middleware self => self\n\n      EMPTY = ''\n      GET = :get\n\n      attr_reader :logger, :starting\n\n      def initialize(app = nil, logger:)\n        @logger = logger\n        super(app)\n      end\n\n      def call(env)\n        @starting = timestamp\n        super\n      end\n\n      # rubocop:disable Layout/LineLength\n      def on_complete(env)\n        summary = SummaryMiddleware.extract(env)\n        logger.info(\"\\e[1m\u001b[35mSQL (#{duration_stats_log(summary)})\u001b\\e[0m #{query(env)};\u001b\")\n        logger.debug(env.request_body) if log_body?(env)\n        logger.info(\"\\e[1m\u001b[36mRead: #{summary.read_rows} rows, #{summary.read_bytes_pretty}. Written: #{summary.written_rows} rows, #{summary.written_bytes_pretty}\\e[0m\")\n      end\n      # rubocop:enable Layout/LineLength\n\n      private\n\n      def duration\n        timestamp - starting\n      end\n\n      def timestamp\n        Process.clock_gettime(Process::CLOCK_MONOTONIC)\n      end\n\n      # @return [Boolean]\n      def log_body?(env)\n        return unless logger.debug?\n        return if env.method == GET # GET queries logs body as a statement\n        return if env.request_body.nil? || env.request_body == EMPTY\n\n        true\n      end\n\n      def query(env)\n        if env.method == GET\n          env.request_body\n        else\n          String(CGI.parse(env.url.query.to_s).dig('query', 0) || '[NO QUERY]').chomp\n        end\n      end\n\n      def duration_stats_log(summary)\n        \"Total: #{Util::Pretty.measure(duration * 1000)}, CH: #{summary.elapsed_pretty}\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/middleware/parse_csv.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    class ParseCsv < ResponseBase\n      Faraday::Response.register_middleware self => self\n\n      # @param env [Faraday::Env]\n      def on_complete(env)\n        return unless content_type?(env, content_type)\n\n        env.body = env.body.strip.empty? ? nil : CSV.parse(env.body)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/middleware/parse_json.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    class ParseJson < ResponseBase\n      Faraday::Response.register_middleware self => self\n\n      # @param env [Faraday::Env]\n      def on_complete(env)\n        return unless content_type?(env, content_type)\n\n        env.body = JSON.parse(env.body, config.json_load_options) unless env.body.strip.empty?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/middleware/parse_json_oj.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    class ParseJsonOj < ResponseBase\n      Faraday::Response.register_middleware self => self\n\n      # @param env [Faraday::Env]\n      def on_complete(env)\n        return unless content_type?(env, content_type)\n\n        env.body = Oj.load(env.body, config.oj_load_options) unless env.body.strip.empty?\n      end\n\n      private\n\n      def on_setup\n        require 'oj' unless defined?(Oj)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/middleware/raise_error.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    class RaiseError < Faraday::Middleware\n      EXCEPTION_CODE_HEADER = 'x-clickhouse-exception-code'\n\n      Faraday::Response.register_middleware self => self\n\n      # @param env [Faraday::Env]\n      def call(env)\n        super\n      rescue Faraday::ConnectionFailed => e\n        raise NetworkException, e.message, e.backtrace\n      end\n\n      # @param env [Faraday::Env]\n      def on_complete(env)\n        if env.response_headers.include?(EXCEPTION_CODE_HEADER) || !env.success?\n          raise DbException, \"[#{env.status}] #{env.body}\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/middleware/response_base.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    class ResponseBase < Faraday::Middleware\n      CONTENT_TYPE_HEADER = 'content-type'\n\n      attr_reader :options\n      attr_reader :content_type\n\n      def initialize(app = nil, options: {}, content_type: nil, preserve_raw: false)\n        super(app)\n        @options = options\n        @content_type = content_type\n        @preserve_raw = preserve_raw\n        on_setup\n      end\n\n      # @return [Boolean]\n      # @param env [Faraday::Env]\n      # @param regexp [NilClass, Regexp]\n      def content_type?(env, regexp)\n        case regexp\n        when NilClass\n          false\n        when Regexp\n          regexp.match?(String(env[:response_headers][CONTENT_TYPE_HEADER]))\n        else\n          raise ArgumentError, \"expected regexp got #{regexp.class}\"\n        end\n      end\n\n      # @return [Config]\n      def config\n        options.fetch(:config)\n      end\n\n      private\n\n      def on_setup\n        # require external dependencies here\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/middleware/summary_middleware.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    class SummaryMiddleware < ResponseBase\n      Faraday::Response.register_middleware self => self\n\n      KEY = :summary\n\n      # @param env [Faraday::Env]\n      # @return [Response::Summary]\n      def self.extract(env)\n        env.custom_members.fetch(KEY)\n      end\n\n      # @param env [Faraday::Env]\n      def on_complete(env)\n        env.custom_members[KEY] = Response::Summary.new(\n          config,\n          headers: env.response_headers,\n          body: env.body.is_a?(Hash) ? env.body : {}\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/middleware.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    autoload :ResponseBase, 'click_house/middleware/response_base'\n    autoload :SummaryMiddleware, 'click_house/middleware/summary_middleware'\n    autoload :Logging, 'click_house/middleware/logging'\n    autoload :ParseCsv, 'click_house/middleware/parse_csv'\n    autoload :ParseJsonOj, 'click_house/middleware/parse_json_oj'\n    autoload :ParseJson, 'click_house/middleware/parse_json'\n    autoload :RaiseError, 'click_house/middleware/raise_error'\n  end\nend\n"
  },
  {
    "path": "lib/click_house/response/factory.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Response\n    class Factory\n      KEY_META = 'meta'\n      KEY_DATA = 'data'\n\n      # @return [ResultSet]\n      # @params faraday [Faraday::Response]\n      # @params config [Config]\n      def self.response(faraday, config)\n        body = faraday.body\n\n        # wrap to be able to use connection#select_one, connection#select_value\n        # with other formats like binary\n        return raw(faraday, config) unless body.is_a?(Hash)\n        return raw(faraday, config) unless body.key?(config.key(KEY_META)) && body.key?(config.key(KEY_DATA))\n\n        ResultSet.new(\n          config: config,\n          meta: body.fetch(config.key(KEY_META)),\n          data: body.fetch(config.key(KEY_DATA)),\n          summary: Middleware::SummaryMiddleware.extract(faraday.env)\n        )\n      end\n\n      # @return [ResultSet]\n      # Rae ResultSet (without type casting)\n      def self.raw(faraday, config)\n        ResultSet.raw(\n          config: config,\n          data: Util.array(faraday.body),\n          summary: Middleware::SummaryMiddleware.extract(faraday.env)\n        )\n      end\n\n      # Result of execution\n      # @return [Response::Summary]\n      # @params faraday [Faraday::Response]\n      def self.exec(faraday)\n        Middleware::SummaryMiddleware.extract(faraday.env)\n      end\n\n      # @return [Response::Summary]\n      def self.empty_exec(config)\n        Summary.new(config)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/response/result_set.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Response\n    class ResultSet\n      extend Forwardable\n      include Enumerable\n\n      KEY_META_NAME = 'name'\n      KEY_META_TYPE = 'type'\n\n      def_delegators :to_a,\n                     :inspect, :each, :fetch, :length, :count, :size,\n                     :first, :last, :[], :to_h\n\n      def_delegators :summary,\n                     :statistics, :headers,\n                     :totals, :rows_before_limit_at_least\n\n      attr_reader :config, :meta, :data, :summary\n\n      class << self\n        # @param config [Config]\n        # @return [ResultSet]\n        def raw(config:, data:, summary:)\n          new(config: config, data: data, to_a: data, meta: [], summary: summary)\n        end\n      end\n\n      # @param config [Config]\n      # @param meta [Array]\n      # @param data [Array]\n      # @param summary [Response::Summary]\n      def initialize(config:, meta:, data:, summary:, to_a: nil)\n        @config = config\n        @meta = meta\n        @data = data\n        @summary = summary\n        @to_a = to_a\n      end\n\n      # @return [Array, Hash]\n      # @param data [Array, Hash]\n      def serialize(data)\n        case data\n        when Hash\n          serialize_one(data)\n        when Array\n          data.map(&method(:serialize_one))\n        else\n          raise ArgumentError, \"expect Hash or Array, got: #{data.class}\"\n        end\n      end\n\n      # @return [Hash]\n      # @param row [Hash]\n      def serialize_one(row)\n        row.each_with_object({}) do |(key, value), object|\n          object[key] = serialize_column(key, value)\n        end\n      end\n\n      # @param name [String] column name\n      # @param value [Any]\n      def serialize_column(name, value)\n        stmt = types.fetch(name)\n        serialize_type(stmt, value)\n      rescue KeyError => e\n        raise SerializeError, \"field <#{name}> does not exists in table schema: #{types}\", e.backtrace\n      rescue StandardError => e\n        raise SerializeError, \"failed to serialize <#{name}> with #{stmt}, #{e.class}, #{e.message}\", e.backtrace\n      end\n\n      def to_a\n        @to_a ||= data.each do |row|\n          row.each do |name, value|\n            row[name] = cast_type(types.fetch(name), value)\n          end\n        end\n      end\n\n      # @return [Hash<String, Ast::Statement>]\n      def types\n        @types ||= meta.each_with_object({}) do |row, object|\n          column = row.fetch(config.key(KEY_META_NAME))\n          # make symbol keys, if config.symbolize_keys is true,\n          # to be able to cast and serialize properly\n          object[config.key(column)] = begin\n            current = Ast::Parser.new(row.fetch(config.key(KEY_META_TYPE))).parse\n            assign_type(current)\n            current\n          end\n        end\n      end\n\n      private\n\n      # @param stmt [Ast::Statement]\n      def assign_type(stmt)\n        stmt.caster = ClickHouse.types[stmt.name]\n\n        if stmt.caster.is_a?(Type::UndefinedType)\n          placeholders = stmt.arguments.map(&:placeholder)\n          stmt.caster = ClickHouse.types[\"#{stmt.name}(#{placeholders.join(', ')})\"]\n        end\n\n        stmt.arguments.each(&method(:assign_type))\n      end\n\n      # @param stmt [Ast::Statement]\n      def cast_type(stmt, value)\n        return cast_container(stmt, value) if stmt.caster.container?\n        return cast_map(stmt, Hash(value)) if stmt.caster.map?\n        return cast_tuple(stmt, Array(value)) if stmt.caster.tuple?\n\n        stmt.caster.cast(value, *stmt.argument_values)\n      end\n\n      # @return [Hash]\n      # @param stmt [Ast::Statement]\n      # @param hash [Hash]\n      def cast_map(stmt, hash)\n        raise ArgumentError, \"expect hash got #{hash.class}\" unless hash.is_a?(Hash)\n\n        key_type, value_type = stmt.arguments\n        hash.each_with_object({}) do |(key, value), object|\n          object[cast_type(key_type, key)] = cast_type(value_type, value)\n        end\n      end\n\n      # @param stmt [Ast::Statement]\n      def cast_container(stmt, value)\n        stmt.caster.cast_each(value) do |item|\n          cast_type(stmt.argument_first!, item)\n        end\n      end\n\n      # @param stmt [Ast::Statement]\n      def cast_tuple(stmt, value)\n        value.map.with_index do |item, ix|\n          cast_type(stmt.arguments.fetch(ix), item)\n        end\n      end\n\n      # @param stmt [Ast::Statement]\n      def serialize_type(stmt, value)\n        return serialize_container(stmt, value) if stmt.caster.container?\n        return serialize_map(stmt, value) if stmt.caster.map?\n        return serialize_tuple(stmt, Array(value)) if stmt.caster.tuple?\n\n        stmt.caster.serialize(value, *stmt.argument_values)\n      end\n\n      # @param stmt [Ast::Statement]\n      def serialize_container(stmt, value)\n        stmt.caster.serialize_each(value) do |item|\n          serialize_type(stmt.argument_first!, item)\n        end\n      end\n\n      # @return [Hash]\n      # @param stmt [Ast::Statement]\n      # @param hash [Hash]\n      def serialize_map(stmt, hash)\n        raise ArgumentError, \"expect hash got #{hash.class}\" unless hash.is_a?(Hash)\n\n        key_type, value_type = stmt.arguments\n        hash.each_with_object({}) do |(key, value), object|\n          object[serialize_type(key_type, key)] = serialize_type(value_type, value)\n        end\n      end\n\n      # @param stmt [Ast::Statement]\n      def serialize_tuple(stmt, value)\n        value.map.with_index do |item, ix|\n          serialize_type(stmt.arguments.fetch(ix), item)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/response/summary.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Response\n    class Summary\n      SUMMARY_HEADER = 'x-clickhouse-summary'\n      KEY_TOTALS = 'totals'\n      KEY_STATISTICS = 'statistics'\n      KEY_ROWS_BEFORE_LIMIT_AT_LEAST = 'rows_before_limit_at_least'\n      KEY_STAT_ELAPSED = 'elapsed'\n\n      attr_reader :config,\n                  :headers,\n                  :summary,\n                  # {:elapsed=>0.387287e-3, :rows_read=>0, :bytes_read=>0}}\n                  :statistics,\n                  :totals,\n                  :rows_before_limit_at_least\n\n      # @param config [Config]\n      # @param headers [Faraday::Utils::Headers]\n      # @param body [Hash]\n      # TOTALS [Array|Hash|NilClass] Support for 'GROUP BY WITH TOTALS' modifier\n      #   https://clickhouse.tech/docs/en/sql-reference/statements/select/group-by/#with-totals-modifier\n      #   Hash in JSON format and Array in JSONCompact\n      def initialize(config, headers: Faraday::Utils::Headers.new, body: {})\n        @headers = headers\n        @config = config\n        @statistics = body.fetch(config.key(KEY_STATISTICS), {})\n        @totals = body[config.key(KEY_TOTALS)]\n        @rows_before_limit_at_least = body[config.key(KEY_ROWS_BEFORE_LIMIT_AT_LEAST)]\n        @summary = parse_summary(headers[SUMMARY_HEADER])\n      end\n\n      # @return [Integer]\n      def read_rows\n        summary[config.key('read_rows')].to_i\n      end\n\n      # @return [Integer]\n      def read_bytes\n        summary[config.key('read_bytes')].to_i\n      end\n\n      # @return [String]\n      def read_bytes_pretty\n        Util::Pretty.size(read_bytes)\n      end\n\n      # @return [Integer]\n      def written_rows\n        summary[config.key('written_rows')].to_i\n      end\n\n      # @return [Integer]\n      def written_bytes\n        summary[config.key('written_bytes')].to_i\n      end\n\n      # @return [String]\n      def written_bytes_pretty\n        Util::Pretty.size(written_bytes)\n      end\n\n      # @return [Integer]\n      def total_rows_to_read\n        summary[config.key('total_rows_to_read')].to_i\n      end\n\n      # @return [Integer]\n      def result_rows\n        summary[config.key('result_rows')].to_i\n      end\n\n      # @return [Integer]\n      def result_bytes\n        summary[config.key('result_bytes')].to_i\n      end\n\n      # @return [Float]\n      def elapsed\n        statistics[config.key(KEY_STAT_ELAPSED)].to_f\n      end\n\n      # @return [String]\n      def elapsed_pretty\n        Util::Pretty.measure(elapsed * 1000)\n      end\n\n      private\n\n      # @return [Hash]\n      # {\n      #   \"read_rows\" => \"1\",\n      #   \"read_bytes\" => \"23\",\n      #   \"written_rows\" => \"1\",\n      #   \"written_bytes\" => \"23\",\n      #   \"total_rows_to_read\" => \"0\",\n      #   \"result_rows\" => \"1\",\n      #   \"result_bytes\" => \"23\",\n      # }\n      def parse_summary(value)\n        return {} if value.nil? || value.empty?\n\n        JSON.parse(value)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/response.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Response\n    autoload :Factory, 'click_house/response/factory'\n    autoload :ResultSet, 'click_house/response/result_set'\n    autoload :Summary, 'click_house/response/summary'\n  end\nend\n"
  },
  {
    "path": "lib/click_house/serializer/base.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Serializer\n    class Base\n      attr_reader :config\n\n      # @param config [Config]\n      def initialize(config)\n        @config = config\n        on_setup\n      end\n\n      def dump(data)\n        raise NotImplementedError, __method__\n      end\n\n      # @return [String]\n      # @param data [Array]\n      def dump_each_row(data, sep = \"\\n\")\n        data.map(&method(:dump)).join(sep)\n      end\n\n      private\n\n      # require external dependencies here\n      def on_setup\n        nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/serializer/json_oj_serializer.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Serializer\n    class JsonOjSerializer < Base\n      def dump(data)\n        Oj.dump(data, config.oj_dump_options)\n      end\n\n      private\n\n      def on_setup\n        require 'oj' unless defined?(Oj)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/serializer/json_serializer.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Serializer\n    class JsonSerializer < Base\n      def dump(data)\n        JSON.dump(data)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/serializer.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Serializer\n    autoload :Base, 'click_house/serializer/base'\n    autoload :JsonSerializer, 'click_house/serializer/json_serializer'\n    autoload :JsonOjSerializer, 'click_house/serializer/json_oj_serializer'\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/array_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class ArrayType < BaseType\n      def cast_each(value, *_argv, &block)\n        value.map(&block)\n      end\n\n      def serialize_each(value, *_argv, &block)\n        value.map(&block)\n      end\n\n      def container?\n        true\n      end\n\n      def ddl?\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/base_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class BaseType\n      def cast(_value, *)\n        raise NotImplementedError, __method__\n      end\n\n      def cast_each(_value, *)\n        raise NotImplementedError, __method__\n      end\n\n      def serialize_each(_value, *)\n        raise NotImplementedError, __method__\n      end\n\n      # @return [Boolean]\n      # true if type contains another type like Nullable(T) or Array(T)\n      def container?\n        false\n      end\n\n      # @return [Boolean]\n      # true if type is a Map\n      def map?\n        false\n      end\n\n      # @return [Boolean]\n      # true if type is a Tuple\n      def tuple?\n        false\n      end\n\n      # @return [Boolean]\n      # skip type from DDL statements\n      def ddl?\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/boolean_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class BooleanType < BaseType\n      TRUE_VALUE = 1\n      FALSE_VALUE = 0\n\n      def cast(value)\n        case value\n        when TrueClass, FalseClass\n          value\n        else\n          value.to_i == TRUE_VALUE\n        end\n      end\n\n      def serialize(value)\n        value ? TRUE_VALUE : FALSE_VALUE\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/date_time64_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class DateTime64Type < BaseType\n      BASE_FORMAT = '%Y-%m-%d %H:%M:%S'\n      CAST_FORMAT = \"#{BASE_FORMAT}.%N\"\n      SERIALIZE_FORMATS = {\n        0 => BASE_FORMAT,\n        1 => \"#{BASE_FORMAT}.%1N\",\n        2 => \"#{BASE_FORMAT}.%2N\",\n        3 => \"#{BASE_FORMAT}.%3N\",\n        4 => \"#{BASE_FORMAT}.%4N\",\n        5 => \"#{BASE_FORMAT}.%5N\",\n        6 => \"#{BASE_FORMAT}.%6N\",\n        7 => \"#{BASE_FORMAT}.%7N\",\n        8 => \"#{BASE_FORMAT}.%8N\",\n        9 => \"#{BASE_FORMAT}.%9N\",\n      }.freeze\n\n      # Tick size (precision):\n      #   10-precision seconds.\n      #   Valid range: [ 0 : 9 ].\n      #   Typically are used - 3 (milliseconds), 6 (microseconds), 9 (nanoseconds).\n      def cast(value, precision = 0, tz = nil)\n        format = precision.zero? ? BASE_FORMAT : CAST_FORMAT\n\n        if tz\n          Time.find_zone(tz).strptime(value, format)\n        else\n          Time.strptime(value, format)\n        end\n      end\n\n      def serialize(value, precision = 3, _tz = nil)\n        value.strftime(SERIALIZE_FORMATS.fetch(precision))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/date_time_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class DateTimeType < BaseType\n      FORMAT = '%Y-%m-%d %H:%M:%S'\n\n      def cast(value, tz = nil)\n        if tz\n          Time.find_zone(tz).strptime(value, FORMAT)\n        else\n          Time.strptime(value, FORMAT)\n        end\n      end\n\n      def serialize(value, *)\n        value.strftime(FORMAT)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/date_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class DateType < BaseType\n      FORMAT = '%Y-%m-%d'\n\n      def cast(value)\n        Date.strptime(value, FORMAT)\n      end\n\n      def serialize(value)\n        value.strftime(FORMAT)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/decimal_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class DecimalType < BaseType\n      MAXIMUM = Float::DIG.next\n\n      # clickhouse:\n      # P - precision. Valid range: [ 1 : 76 ]. Determines how many decimal digits number can have (including fraction).\n      # S - scale. Valid range: [ 0 : P ]. Determines how many decimal digits fraction can have.\n      #\n      # when Oj parser @refs https://stackoverflow.com/questions/47885304/deserialise-json-numbers-as-bigdecimal\n      def cast(value, precision = MAXIMUM, _scale = nil)\n        case value\n        when BigDecimal\n          value\n        when String\n          BigDecimal(value)\n        else\n          BigDecimal(value, precision > MAXIMUM ? MAXIMUM : precision)\n        end\n      end\n\n      # @return [BigDecimal]\n      def serialize(value, precision = MAXIMUM, _scale = nil)\n        cast(value, precision)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/fixed_string_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class FixedStringType < BaseType\n      def cast(value, _limit = nil)\n        value.to_s\n      end\n\n      def serialize(value, limit = nil)\n        value[0..(limit ? limit.pred : -1)] unless value.nil?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/float_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class FloatType < BaseType\n      def cast(value)\n        Float(value) unless value.nil?\n      end\n\n      def serialize(value)\n        value.to_f unless value.nil?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/integer_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class IntegerType < BaseType\n      def cast(value)\n        Integer(value)\n      end\n\n      def serialize(value)\n        value.to_i\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/ip_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class IPType < BaseType\n      def cast(value)\n        IPAddr.new(value)\n      end\n\n      def serialize(value)\n        value.to_s\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/low_cardinality_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class LowCardinalityType < BaseType\n      def cast_each(value, *_argv)\n        yield(value)\n      end\n\n      def serialize_each(value, *_argv)\n        yield(value)\n      end\n\n      def container?\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/map_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class MapType < BaseType\n      def map?\n        true\n      end\n\n      def ddl?\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/nullable_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class NullableType < BaseType\n      def cast_each(value, *_argv)\n        yield(value) unless value.nil?\n      end\n\n      def serialize_each(value, *_argv)\n        yield(value) unless value.nil?\n      end\n\n      def container?\n        true\n      end\n\n      def ddl?\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/string_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class StringType < BaseType\n      def cast(value, *)\n        value.to_s\n      end\n\n      def serialize(value, *)\n        value.to_s\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/tuple_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class TupleType < BaseType\n      def tuple?\n        true\n      end\n\n      def ddl?\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type/undefined_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class UndefinedType < BaseType\n      def cast(value, *)\n        value\n      end\n\n      def serialize(value, *)\n        value\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/type.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    autoload :BaseType, 'click_house/type/base_type'\n    autoload :NullableType, 'click_house/type/nullable_type'\n    autoload :UndefinedType, 'click_house/type/undefined_type'\n    autoload :DateType, 'click_house/type/date_type'\n    autoload :DateTimeType, 'click_house/type/date_time_type'\n    autoload :DateTime64Type, 'click_house/type/date_time64_type'\n    autoload :IntegerType, 'click_house/type/integer_type'\n    autoload :FloatType, 'click_house/type/float_type'\n    autoload :BooleanType, 'click_house/type/boolean_type'\n    autoload :DecimalType, 'click_house/type/decimal_type'\n    autoload :FixedStringType, 'click_house/type/fixed_string_type'\n    autoload :ArrayType, 'click_house/type/array_type'\n    autoload :TupleType, 'click_house/type/tuple_type'\n    autoload :MapType, 'click_house/type/map_type'\n    autoload :StringType, 'click_house/type/string_type'\n    autoload :IPType, 'click_house/type/ip_type'\n    autoload :LowCardinalityType, 'click_house/type/low_cardinality_type'\n  end\nend\n"
  },
  {
    "path": "lib/click_house/util/pretty.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Util\n    module Pretty\n      SIZE_UNITS = %w[B KiB MiB GiB TiB Pib EiB].freeze\n\n      module_function\n\n      # rubocop:disable all\n      def size(bytes)\n        return '0B' if bytes == 0\n\n        exp = (Math.log(bytes) / Math.log(1024)).to_i\n        exp = 6 if exp > 6\n\n        format('%.1f%s', bytes.to_f / 1024**exp, SIZE_UNITS[exp])\n      end\n      # rubocop:enable all\n\n      def measure(ms)\n        \"#{ms.round}MS\"\n      end\n\n      def squish(string)\n        string.gsub(/[[:space:]]+/, ' ').strip\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/util/statement.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Util\n    module Statement\n      module_function\n\n      def ensure(truthful, value, fallback = nil)\n        truthful ? value : fallback\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/util.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Util\n    autoload :Statement, 'click_house/util/statement'\n    autoload :Pretty, 'click_house/util/pretty'\n\n    module_function\n\n    # wraps\n    def array(input)\n      input.is_a?(Array) ? input : [input]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/click_house/version.rb",
    "content": "# frozen_string_literal: true\n\nmodule ClickHouse\n  VERSION = '2.1.2'\nend\n"
  },
  {
    "path": "lib/click_house.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'date'\nrequire 'json'\nrequire 'csv'\nrequire 'uri'\nrequire 'logger'\nrequire 'faraday'\nrequire 'forwardable'\nrequire 'bigdecimal'\nrequire 'active_support/core_ext/time/calculations'\nrequire 'click_house/version'\nrequire 'click_house/errors'\nrequire 'click_house/response'\nrequire 'click_house/serializer'\nrequire 'click_house/type'\nrequire 'click_house/middleware'\nrequire 'click_house/extend'\nrequire 'click_house/ast'\nrequire 'click_house/util'\nrequire 'click_house/definition'\n\nmodule ClickHouse\n  extend Extend::TypeDefinition\n  extend Extend::Configurable\n  extend Extend::Connectible\n\n  autoload :Config, 'click_house/config'\n  autoload :Connection, 'click_house/connection'\n\n  add_type 'Array', Type::ArrayType.new\n  add_type 'Nullable', Type::NullableType.new\n  add_type 'Map', Type::MapType.new\n  add_type 'LowCardinality', Type::LowCardinalityType.new\n  add_type 'Tuple', Type::TupleType.new\n\n  %w[Bool].each do |column|\n    add_type column, Type::BooleanType.new\n  end\n\n  %w[Date].each do |column|\n    add_type column, Type::DateType.new\n  end\n\n  %w[String FixedString(%d) UUID].each do |column|\n    add_type column, Type::StringType.new\n  end\n\n  %w[DateTime DateTime(%s)].each do |column|\n    add_type column, Type::DateTimeType.new\n  end\n\n  ['DateTime64(%d)', 'DateTime64(%d, %s)'].each do |column|\n    add_type column, Type::DateTime64Type.new\n  end\n\n  ['Decimal(%d, %d)', 'Decimal32(%d)', 'Decimal64(%d)', 'Decimal128(%d)', 'Decimal256(%d)'].each do |column|\n    add_type column, Type::DecimalType.new\n  end\n\n  %w[UInt8 UInt16 UInt32 UInt64 Int8 Int16 Int32 Int64].each do |column|\n    add_type column, Type::IntegerType.new\n  end\n\n  %w[Float32 Float64].each do |column|\n    add_type column, Type::FloatType.new\n  end\n\n  %w[IPv4 IPv6].each do |column|\n    add_type column, Type::IPType.new\n  end\nend\n"
  },
  {
    "path": "log/.keep",
    "content": ""
  },
  {
    "path": "spec/click_house/ast/parser_spec.rb",
    "content": "RSpec.describe ClickHouse::Ast::Parser do\n  let(:expectations) do\n    {\n      \"Int\" => 'Int',\n      \"DateTime('Asia/Istanbul')\" => \"DateTime('Asia/Istanbul')\",\n      \"Array(Int, String(2))\" => \"Array(Int,String(2))\",\n      \"Array(Array(Array(Array(Nullable(Int, String)))))\" => \"Array(Array(Array(Array(Nullable(Int,String)))))\",\n      \"Function(Decimal(1, 2), Map(Decimal(3, 4), Decimal(5, 6)))\" => \"Function(Decimal(1,2),Map(Decimal(3,4),Decimal(5,6)))\",\n      \"Array(Map(Decimal(1, 2), Decimal(3, 4)))\" => \"Array(Map(Decimal(1,2),Decimal(3,4)))\",\n      \"Map(Decimal(1, 2), Decimal(3, 4))\" => \"Map(Decimal(1,2),Decimal(3,4))\",\n      \"Map(Decimal(1,2))\" => \"Map(Decimal(1,2))\",\n      \"Map(String, Decimal(1,2))\" => \"Map(String,Decimal(1,2))\",\n      \"Decimal(1,2)\" => \"Decimal(1,2)\",\n      \"A(1)\" => \"A(1)\",\n      \"A(1, 2)\" => \"A(1,2)\",\n      \"A(B(1))\" => \"A(B(1))\",\n      \"A(B(1), B(2))\" => \"A(B(1),B(2))\",\n      \"Enum8('hello' = 1, 'world' = 2)\" => \"Enum8('hello' = 1,'world' = 2)\"\n    }\n  end\n\n  it 'works' do\n    expectations.each do |statement, expect|\n      expect(described_class.new(statement).parse.to_s).to eq(expect)\n    end\n  end\n\n  context 'when Array with nested type' do\n    subject do\n      described_class.new(\"Array(String(2))\").parse\n    end\n\n    it 'works' do\n      expect(subject.name).to eq('Array')\n      expect(subject.arguments).to have_attributes(size: 1)\n      expect(subject.arguments.first.name).to eq(\"String\")\n      expect(subject.arguments.first.arguments).to have_attributes(size: 1)\n      expect(subject.arguments.first.arguments.first.name).to eq(\"2\")\n    end\n  end\n\n  context 'when space between arguments' do\n    subject do\n      described_class.new(\"Foo(10, 'bar')\").parse\n    end\n\n    it 'works' do\n      expect(subject.name).to eq('Foo')\n      expect(subject.arguments.map(&:placeholder)).to eq(%w[%d %s])\n      expect(subject.arguments.map(&:value)).to eq([10, 'bar'])\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/config_spec.rb",
    "content": "RSpec.describe ClickHouse::Config do\n  describe '#assign' do\n    it 'works' do\n      expect { subject.assign(port: 33) }.to change { subject.port }.to(33)\n    end\n\n    it 'returns self' do\n      expect(subject.assign({})).to be_a(described_class)\n    end\n  end\n\n  describe '#initialize' do\n    context 'when params' do\n      it 'works' do\n        expect(described_class.new(port: 33).port).to eq(33)\n      end\n    end\n\n    context 'when block' do\n      it 'works' do\n        expect(described_class.new { |c| c.port = 33 }.port).to eq(33)\n      end\n    end\n  end\n\n  describe '#auth?' do\n    context 'when credentials empty' do\n      before do\n        subject.username = nil\n        subject.password = nil\n      end\n\n      it 'is false' do\n        expect(subject.auth?).to eq(false)\n      end\n    end\n\n    context 'when credentials exists' do\n      before do\n        subject.username = 'foo'\n        subject.password = 'bar'\n      end\n\n      it 'is true' do\n        expect(subject.auth?).to eq(true)\n      end\n    end\n  end\n\n  describe '#url!' do\n    before do\n      subject.url = 'http://example.com'\n      subject.scheme = 'https'\n      subject.host = 'clickhouse'\n      subject.port = '3344'\n    end\n\n    context 'when url exists' do\n      it 'works' do\n        expect(subject.url!).to eq('http://example.com')\n      end\n    end\n\n    context 'when url empty' do\n      before do\n        subject.url = nil\n      end\n\n      it 'works' do\n        expect(subject.url!).to eq('https://clickhouse:3344')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/connection_spec.rb",
    "content": "RSpec.describe ClickHouse::Connection do\n  context 'when basic auth' do\n    subject do\n      ClickHouse::Connection.new(ClickHouse.config.clone.assign(\n        username: 'user',\n        password: 'password'\n      ))\n    end\n\n    it 'works' do\n      expect {  subject.tables }.to raise_error(ClickHouse::DbException, /Authentication failed/)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/definition/column_set_spec.rb",
    "content": "RSpec.describe ClickHouse::Definition::ColumnSet do\n  def squish(string)\n    string.gsub(/[[:space:]]/, '').strip\n  end\n\n  context 'when integration' do\n    subject do\n      described_class.new do |t|\n        t.Decimal :money, 5, 5\n        t.UInt16  :year_birth, low_cardinality: true\n        t.UInt16  :year_death, low_cardinality: true, nullable: true, default: 0\n        t.Float32 :city_id, default: 0, nullable: true\n        t.Nested :json do |n|\n          n.UInt8 :cid, nullable: true\n          n.Date  :created_at, default: 'NOW()'\n          n.DateTime :updated_at, 'UTC'\n          n.DateTime64 :deleted_at, 6, 'UTC'\n        end\n        t << \"words Enum('hello' = 1, 'world' = 2)\"\n        t << \"tags Array(String)\"\n      end\n    end\n\n    let(:expectation) do\n      <<~SQL\n        ( \n          money Decimal(5, 5), \n          year_birth LowCardinality(UInt16), \n          year_death LowCardinality(Nullable(UInt16)) DEFAULT 0,\n          city_id Nullable(Float32) DEFAULT 0, \n          json Nested ( \n                        cid Nullable(UInt8) , \n                        created_at Date DEFAULT NOW(), \n                        updated_at DateTime('UTC'),\n                        deleted_at DateTime64(6, 'UTC')  \n                      ), \n          words Enum('hello' = 1, 'world' = 2), \n          tags Array(String) \n        )\n      SQL\n    end\n\n    it 'works' do\n      expect(squish(subject.to_s)).to eq(squish(expectation))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/extend/connection_altering_spec.rb",
    "content": "RSpec.describe ClickHouse::Extend::ConnectionAltering do\n  subject do\n    ClickHouse.connection\n  end\n\n  describe '#add_column' do\n    before do\n      subject.execute <<~SQL\n        CREATE TABLE rspec (date Date, id UInt32, user_id UInt32) ENGINE MergeTree() ORDER BY date\n      SQL\n    end\n\n    context 'when not exists' do\n      before do\n        subject.add_column(:rspec, :account_id, :UInt64, default: 0, after: :date)\n      end\n\n      let(:column) do\n        subject.describe_table('rspec').find { |r| r['name'] == 'account_id' }\n      end\n\n      it 'works' do\n        expect(subject.describe_table('rspec').map { |r| r['name'] }).to eq(%w[date account_id id user_id])\n        expect(column).to include('type' => 'UInt64')\n        expect(column).to include('default_expression' => '0')\n      end\n    end\n\n    context 'when exists' do\n      let(:function) do\n        subject.add_column(:rspec, :user_id, 'UInt32')\n      end\n\n      it 'errors' do\n        expect { function }.to raise_error(ClickHouse::DbException)\n      end\n    end\n\n    context 'when if not exists' do\n      let(:function) do\n        subject.add_column(:rspec, :user_id, 'UInt32', if_not_exists: true)\n      end\n\n      it 'works' do\n        expect(function).to eq(true)\n      end\n    end\n  end\n\n  describe '#drop_column' do\n    before do\n      subject.execute <<~SQL\n        CREATE TABLE rspec (date Date, id UInt32, int_1 UInt32) ENGINE MergeTree() ORDER BY date\n      SQL\n    end\n\n    context 'when exists' do\n      let(:function) do\n        subject.drop_column('rspec', :int_1)\n      end\n\n      it 'works' do\n        expect { function }.to change { subject.describe_table('rspec').length }.by(-1)\n      end\n    end\n\n    context 'when not exists' do\n      let(:function) do\n        subject.drop_column('rspec', :foo)\n      end\n\n      it 'errors' do\n        expect { function }.to raise_error(ClickHouse::DbException)\n      end\n    end\n\n    context 'when if exists' do\n      let(:function) do\n        subject.drop_column('rspec', :foo, if_exists: true)\n      end\n\n      it 'works' do\n        expect(function).to eq(true)\n      end\n    end\n  end\n\n  describe '#modify_column' do\n    before do\n      subject.execute <<~SQL\n        CREATE TABLE rspec (date Date, id UInt32, int_1 UInt32) ENGINE MergeTree() ORDER BY date\n      SQL\n    end\n\n    context 'when exists' do\n      let(:function) do\n        subject.modify_column('rspec', 'int_1', type: :UInt64, default: 0)\n      end\n\n      let(:column) do\n        -> { subject.describe_table('rspec').find { |r| r['name'] == 'int_1' } }\n      end\n\n      it 'works' do\n        expect { function }.to change { column.call.values_at('type', 'default_expression') }.to(['UInt64', '0'])\n      end\n    end\n\n    context 'when not exists' do\n      let(:function) do\n        subject.modify_column('rspec', 'foo', type: :UInt64)\n      end\n\n      it 'errors' do\n        expect { function }.to raise_error(ClickHouse::DbException)\n      end\n    end\n\n    context 'when if exists' do\n      let(:function) do\n        subject.modify_column('rspec', 'foo', type: :UInt64, if_exists: true)\n      end\n\n      it 'works' do\n        expect(function).to eq(true)\n      end\n    end\n  end\n\n  describe '#alter_table' do\n    before do\n      subject.execute <<~SQL\n        CREATE TABLE rspec (date Date, id UInt32, int_1 UInt32) ENGINE MergeTree() ORDER BY date\n      SQL\n    end\n\n    context 'when argument' do\n      let(:function) do\n        subject.alter_table('rspec', 'DROP COLUMN int_1')\n      end\n\n      it 'works' do\n        expect { function }.to change { subject.describe_table('rspec').length }.by(-1)\n      end\n    end\n\n    context 'when block' do\n      let(:function) do\n        subject.alter_table('rspec') do\n          'DROP COLUMN int_1'\n        end\n      end\n\n      it 'works' do\n        expect { function }.to change { subject.describe_table('rspec').length }.by(-1)\n      end\n    end\n  end\n\n  describe '#add_index, #drop_index' do\n    before do\n      subject.execute <<~SQL\n        CREATE TABLE rspec(a String, b Array(String)) engine=MergeTree() order by a;\n      SQL\n    end\n\n    it 'works' do\n      expect(subject.add_index('rspec', 'ix', 'has(b, a)', type: 'minmax', granularity: 2)).to eq(true)\n      expect(subject.drop_index('rspec', 'ix')).to eq(true)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/extend/connection_database_spec.rb",
    "content": "RSpec.describe ClickHouse::Extend::ConnectionDatabase do\n  subject do\n    ClickHouse.connection\n  end\n\n  describe '#databases' do\n    it 'works' do\n      expect(subject.databases).to include('default', 'system', ClickHouse.config.database)\n    end\n  end\n\n  describe '#create_database' do\n    context 'when exists' do\n      it 'errors' do\n        expect { subject.create_database(ClickHouse.config.database) }.to raise_error(ClickHouse::DbException)\n      end\n    end\n\n    context 'when if not exists' do\n      it 'works' do\n        expect(subject.create_database(ClickHouse.config.database, if_not_exists: true)).to eq(true)\n      end\n    end\n\n    context 'when default' do\n      it 'works' do\n        expect(subject.create_database('foo')).to eq(true)\n      end\n    end\n\n    context 'when engine' do\n      it 'works' do\n        expect(subject.create_database('foo', engine: 'Lazy(10)')).to eq(true)\n      end\n    end\n  end\n\n  describe '#drop_database' do\n    context 'when not exists' do\n      it 'errors' do\n        expect { subject.drop_database('foo') }.to raise_error(ClickHouse::DbException)\n      end\n    end\n\n    context 'when exists' do\n      it 'works' do\n        expect(subject.drop_database('foo', if_exists: true)).to eq(true)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/extend/connection_explaining_spec.rb",
    "content": "RSpec.describe ClickHouse::Extend::ConnectionExplaining do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(id Int64) ENGINE TinyLog\n    SQL\n  end\n\n  let(:expectation) do\n    <<~TXT\n      Expression ((Projection + Before ORDER BY))\n        Join (JOIN FillRightFirst)\n          Expression (Before JOIN)\n            ReadFromStorage (TinyLog)\n          Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY))))\n            ReadFromStorage (TinyLog)\n    TXT\n  end\n\n  context 'when normal query' do\n    it 'works' do\n      buffer = StringIO.new\n      subject.explain('SELECT 1 FROM rspec CROSS JOIN rspec', io: buffer)\n      expect(buffer.string).to eq(expectation)\n    end\n  end\n\n  context 'when EXPLAIN query' do\n    it 'works' do\n      buffer = StringIO.new\n      subject.explain('EXPLAIN SELECT 1 FROM rspec CROSS JOIN rspec', io: buffer)\n      expect(buffer.string).to eq(expectation)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/extend/connection_healthy_spec.rb",
    "content": "RSpec.describe ClickHouse::Extend::ConnectionHealthy do\n  subject do\n    ClickHouse::Connection.new(ClickHouse.config)\n  end\n\n  describe '#ping' do\n    context 'when ok' do\n      it 'works' do\n        expect(subject.ping).to eq(true)\n      end\n    end\n\n    context 'when fail' do\n      before do\n        subject.transport.port = '80'\n      end\n\n      it 'errors' do\n        expect { subject.ping }.to raise_error(ClickHouse::NetworkException)\n      end\n    end\n  end\n\n  describe '#replicas_status' do\n    context 'when ok' do\n      it 'works' do\n        expect(subject.replicas_status).to eq(true)\n      end\n    end\n\n    context 'when fail' do\n      before do\n        subject.transport.port = '80'\n      end\n\n      it 'errors' do\n        expect { subject.replicas_status }.to raise_error(ClickHouse::NetworkException)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/extend/connection_inserting_spec.rb",
    "content": "# ⚠️ INSERT IN TESTS SHOULD HAVE A DIFFERENT ORDER OF COLUMNS\n#   FROM THE ORDER IN THE TABLE ITSELF\nRSpec.describe ClickHouse::Extend::ConnectionInserting do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(id Int64, name Nullable(String)) ENGINE Memory\n    SQL\n  end\n\n  def expected(insert, count)\n    expect(insert.written_rows).to eq(count)\n    expect(subject.select_value('SELECT COUNT(*) FROM rspec')).to eq(count)\n  end\n\n  context 'when blank' do\n    let(:insert) do\n      subject.insert('rspec')\n    end\n\n    it 'works' do\n      expected(insert, 0)\n    end\n  end\n\n  context 'when columns with blank values' do\n    let(:insert) do\n      subject.insert('rspec', columns: %i[id name])\n    end\n\n    it 'works' do\n      expected(insert, 0)\n    end\n  end\n\n  describe 'execution' do\n    let(:insert) do\n      subject.insert('rspec', values: {id: 1, name: 'foo'})\n    end\n\n    it 'has proper attributes' do\n      expect(insert.read_rows).to be > 0\n      expect(insert.read_bytes).to be > 0\n      expect(insert.written_rows).to be > 0\n      expect(insert.written_bytes).to be > 0\n      expect(insert.result_rows).to be > 0\n      expect(insert.result_bytes).to be > 0\n      expect(insert.summary).not_to be_empty\n      expect(insert.headers).not_to be_empty\n    end\n  end\n\n  context 'when body', if: ruby_version_gt('3') do\n    context 'when Hash' do\n      let(:insert) do\n        subject.insert('rspec', {id: 1, name: 'foo'})\n      end\n\n      it 'works' do\n        expected(insert, 1)\n      end\n    end\n\n    context 'when Array' do\n      let(:insert) do\n        subject.insert('rspec', [{id: 1, name: 'foo'}, {id: 1, name: 'foo'}])\n      end\n\n      it 'works' do\n        expected(insert, 2)\n      end\n    end\n  end\n\n  context 'when body', if: ruby_version_lt('3') do\n    context 'when Hash' do\n      let(:insert) do\n        subject.insert('rspec', {id: 1, name: 'foo'}, {})\n      end\n\n      it 'works' do\n        expected(insert, 1)\n      end\n    end\n\n    context 'when Array' do\n      let(:insert) do\n        subject.insert('rspec', [{id: 1, name: 'foo'}, {id: 1, name: 'foo'}], {})\n      end\n\n      it 'works' do\n        expected(insert, 2)\n      end\n    end\n  end\n\n  context 'when block with columns' do\n    let(:insert) do\n      subject.insert('rspec', columns: %i[name id], values: [['Sun', 1], ['Moon', 2]])\n    end\n\n    it 'works' do\n      expected(insert, 2)\n    end\n\n    context 'when string format' do\n      let(:insert) do\n        subject.insert('rspec', columns: %i[name id], values: [%w[Sun 1], %w[Moon 2]], format: 'JSONCompactStringsEachRow')\n      end\n\n      it 'works' do\n        expected(insert, 2)\n      end\n    end\n  end\n\n  context 'when argument with columns' do\n    let(:insert) do\n      subject.insert('rspec', columns: %i[name id]) do |buffer|\n        buffer << ['Sun', 1]\n        buffer << ['Moon', 2]\n      end\n    end\n\n    it 'works' do\n      expected(insert, 2)\n    end\n  end\n\n  context 'when hash with argument' do\n    let(:insert) do\n      subject.insert('rspec', values: [{ name: 'Sun', id: 1 }, { name: 'Moon', id: 2 }])\n    end\n\n    it 'works' do\n      expected(insert, 2)\n    end\n  end\n\n  context 'when hash with block' do\n    let(:insert) do\n      subject.insert('rspec') do |buffer|\n        buffer << { name: 'Sun', id: 1 }\n        buffer << { name: 'Moon', id: 2 }\n      end\n    end\n\n    it 'works' do\n      expected(insert, 2)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/extend/connection_selective_spec.rb",
    "content": "RSpec.describe ClickHouse::Extend::ConnectionSelective do\n  subject do\n    ClickHouse.connection\n  end\n\n  describe '#select_value' do\n    context 'when exists' do\n      it 'works' do\n        expect(subject.select_value('SELECT 13')).to eq(13)\n      end\n    end\n\n    context 'when not exists' do\n      it 'works' do\n        expect(subject.select_value('SELECT null')).to eq(nil)\n      end\n    end\n\n    context 'when multiple columns' do\n      it 'works' do\n        expect(subject.select_value('SELECT 1, 2, 3, 4, 5')).to eq(1)\n      end\n    end\n  end\n\n  describe '#select_one' do\n    context 'when exists' do\n      it 'works' do\n        expect(subject.select_one('SELECT 1 AS foo, 2 AS bar')).to eq({ 'foo' => 1, 'bar' => 2 })\n      end\n    end\n\n    context 'when not exists' do\n      it 'works' do\n        expect(subject.select_one('SELECT NULL')).to eq({ 'NULL' => nil })\n      end\n    end\n  end\n\n  describe '#select_all' do\n    before do\n      subject.execute <<~SQL\n        CREATE TABLE rspec (date Date, id UInt32) ENGINE TinyLog\n      SQL\n\n      subject.execute <<~SQL\n        INSERT INTO rspec (date, id) VALUES('2000-01-01', 1), ('2000-01-02', 2)\n      SQL\n    end\n\n    context 'when empty' do\n      it 'works' do\n        expect(subject.select_all('SELECT * FROM rspec where id = 100').to_a).to eq([])\n      end\n    end\n\n    context 'when exists' do\n      let(:expectation) do\n        [\n          { 'date' => Date.new(2000, 1, 1), 'id' => 1 },\n          { 'date' => Date.new(2000, 1, 2), 'id' => 2 }\n        ]\n      end\n\n      it 'works' do\n        expect(subject.select_all('SELECT * FROM rspec').to_a).to match_array(expectation)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/extend/connection_table_spec.rb",
    "content": "RSpec.describe ClickHouse::Extend::ConnectionTable do\n  subject do\n    ClickHouse.connection\n  end\n\n  describe '#tables' do\n    context 'when empty' do\n      it 'works' do\n        expect(subject.tables).to eq([])\n      end\n    end\n\n    context 'when exists' do\n      before do\n        subject.execute <<~SQL\n          CREATE TABLE rspec (date Date, id UInt32) ENGINE TinyLog\n        SQL\n      end\n\n      it 'works' do\n        expect(subject.tables).to contain_exactly('rspec')\n      end\n    end\n  end\n\n  describe '#table_exists?' do\n    context 'when not exists' do\n      it 'works' do\n        expect(subject.table_exists?('foo')).to eq(false)\n      end\n    end\n\n    context 'when exists' do\n      before do\n        subject.execute <<~SQL\n          CREATE TABLE rspec (date Date, id UInt32) ENGINE TinyLog\n        SQL\n      end\n\n      it 'works' do\n        expect(subject.table_exists?('rspec')).to eq(true)\n      end\n    end\n  end\n\n  describe '#describe_table' do\n    context 'when nested' do\n      before do\n        subject.execute <<~SQL\n          CREATE TABLE rspec (\n            date Date,\n            id UInt32,\n            json Nested (uid UInt32)\n          ) ENGINE TinyLog\n        SQL\n      end\n\n      let(:expectation) do\n        [\n          {'name' =>'date', 'type' =>'Date', 'default_type' =>'', 'default_expression' =>'', 'comment' =>'', 'codec_expression' =>'', 'ttl_expression' =>''},\n          {'name' =>'id', 'type' =>'UInt32', 'default_type' =>'', 'default_expression' =>'', 'comment' =>'', 'codec_expression' =>'', 'ttl_expression' =>''},\n          {'name' =>'json.uid', 'type' =>'Array(UInt32)', 'default_type' =>'', 'default_expression' =>'', 'comment' =>'', 'codec_expression' =>'', 'ttl_expression' =>''}\n        ]\n      end\n\n      it 'works' do\n        expect(subject.describe_table('rspec').to_a).to eq(expectation)\n      end\n    end\n\n    context 'when table not exists' do\n      it 'errors' do\n        expect { subject.describe_table('foo') }.to raise_error(ClickHouse::DbException)\n      end\n    end\n  end\n\n  describe '#drop_table' do\n    context 'when not exists' do\n      it 'errors' do\n        expect { subject.drop_table('foo') }.to raise_error(ClickHouse::DbException)\n      end\n    end\n\n    context 'when if exists' do\n      it 'works' do\n        expect(subject.drop_table('foo', if_exists: true)).to eq(true)\n      end\n    end\n\n    context 'when default' do\n      before do\n        subject.execute <<~SQL\n          CREATE TABLE rspec (date Date, id UInt32) ENGINE TinyLog\n        SQL\n      end\n\n      it 'works' do\n        expect(subject.drop_table('rspec')).to eq(true)\n      end\n    end\n  end\n\n  describe '#truncate_table' do\n    context 'when table exists' do\n      before do\n        subject.execute <<~SQL\n          CREATE TABLE rspec(id Int64) ENGINE TinyLog\n        SQL\n\n        subject.insert('rspec', columns: %i[id], values: [[1]])\n      end\n\n      it 'works' do\n        expect { subject.truncate_table('rspec') }.to change { subject.select_value('SELECT COUNT(*) from rspec') }.from(1).to(0)\n      end\n    end\n\n    context 'when table not exists' do\n      it 'errors' do\n        expect { subject.truncate_table('rspec') }.to raise_error(ClickHouse::DbException)\n      end\n    end\n\n    context 'when if exists' do\n      it 'works' do\n        expect(subject.truncate_table('rspec', if_exists: true)).to eq(true)\n      end\n    end\n  end\n\n  describe '#truncate_tables' do\n    before do\n      subject.execute <<~SQL\n        CREATE TABLE rspec_1(id Int64) ENGINE TinyLog\n      SQL\n\n      subject.execute <<~SQL\n        CREATE TABLE rspec_2(id Int64) ENGINE TinyLog\n      SQL\n\n      subject.insert('rspec_1', columns: %i[id], values: [[1]])\n      subject.insert('rspec_2', columns: %i[id], values: [[1]])\n    end\n\n    it 'works' do\n      sql = <<~SQL\n        SELECT (SELECT COUNT(*) FROM rspec_1) + (SELECT COUNT(*) FROM rspec_2)\n      SQL\n\n      expect { subject.truncate_tables }.to change { subject.select_value(sql) }.from(2).to(0)\n    end\n  end\n\n  describe '#rename_table' do\n    before do\n      subject.execute <<~SQL\n        CREATE TABLE bar(id Int64) ENGINE TinyLog\n      SQL\n\n      subject.execute <<~SQL\n        CREATE TABLE foo(id Int64) ENGINE TinyLog\n      SQL\n    end\n\n    context 'when 1 to 1' do\n      it 'works' do\n        expect { subject.rename_table('bar', 'baz') }.to change { subject.tables }.from(%w[bar foo]).to(%w[baz foo])\n      end\n    end\n\n    context 'when many to many' do\n      it 'works' do\n        expect { subject.rename_table(%w[bar foo], %w[baz foz]) }.to change { subject.tables }.from(%w[bar foo]).to(%w[baz foz])\n      end\n    end\n\n    context 'when incorrect arity' do\n      it 'errors' do\n        expect { subject.rename_table(%w[bar foo], %w[baz]) }.to raise_error(ClickHouse::StatementException)\n      end\n    end\n  end\n\n  describe '#create_table' do\n    context 'when column options' do\n      before do\n        subject.create_table('rspec', engine: 'MergeTree()', order: 'date') do |t|\n          t.UInt16      :id, 16, default: 0, ttl: 'date + INTERVAL 1 DAY'\n          t.UInt16      :year\n          t.IPv4        :ipv4\n          t.IPv6        :ipv6\n          t.Date        :date\n          t.DateTime    :time, 'UTC'\n          t.DateTime64  :time_with_usec, 4, 'UTC'\n          t.Decimal     :money, 5, 4\n          t.String      :event, nullable: true\n          t.Nested      :json do |n|\n            n.UInt8     :cid\n            n.Date      :created_at\n          end\n          t << \"vendor Enum('microsoft' = 1, 'apple' = 2)\"\n        end\n      end\n\n      let(:columns) do\n        subject.describe_table('rspec').each_with_object({}) do |column, object|\n          object[column.fetch('name')] = column\n        end\n      end\n\n      it 'works' do\n        expect(columns.fetch('id')).to include('type' => 'UInt16', 'default_expression' => '0', 'ttl_expression' => 'date + toIntervalDay(1)')\n        expect(columns.fetch('year')).to include('type' => 'UInt16', 'default_expression' => '', 'ttl_expression' => '')\n        expect(columns.fetch('ipv4')).to include('type' => 'IPv4', 'default_expression' => '', 'ttl_expression' => '')\n        expect(columns.fetch('ipv6')).to include('type' => 'IPv6', 'default_expression' => '', 'ttl_expression' => '')\n        expect(columns.fetch('date')).to include('type' => 'Date', 'default_expression' => '', 'ttl_expression' => '')\n        expect(columns.fetch('time')).to include('type' => \"DateTime('UTC')\", 'default_expression' => '', 'ttl_expression' => '')\n        expect(columns.fetch('time_with_usec')).to include('type' => \"DateTime64(4, 'UTC')\", 'default_expression' => '', 'ttl_expression' => '')\n        expect(columns.fetch('money')).to include('type' => 'Decimal(5, 4)', 'default_expression' => '', 'ttl_expression' => '')\n        expect(columns.fetch('event')).to include('type' => 'Nullable(String)', 'default_expression' => '', 'ttl_expression' => '')\n        expect(columns.fetch('json.cid')).to include('type' => 'Array(UInt8)', 'default_expression' => '', 'ttl_expression' => '')\n        expect(columns.fetch('json.created_at')).to include('type' => 'Array(Date)', 'default_expression' => '', 'ttl_expression' => '')\n        expect(columns.fetch('vendor')).to include('type' => \"Enum8('microsoft' = 1, 'apple' = 2)\")\n      end\n    end\n\n    context 'when table options' do\n      before do\n        subject.create_table('rspec',\n          order: 'year',\n          ttl: 'date + INTERVAL 1 DAY',\n          sample: 'year',\n          settings: 'index_granularity=8192',\n          primary_key: 'year',\n          engine: 'MergeTree()') do |t|\n          t.UInt16  :year\n          t.Date    :date\n        end\n      end\n\n      let(:schema) do\n        subject.execute('SHOW CREATE rspec FORMAT TabSeparatedRaw').body\n      end\n\n      let(:expectation) do\n        <<~SQL\n          CREATE TABLE click_house_rspec.rspec \n          (\n              `year` UInt16, \n              `date` Date\n          ) \n          ENGINE = MergeTree \n          PRIMARY KEY year \n          ORDER BY year \n          SAMPLE BY year\n          TTL date + toIntervalDay(1) \n          SETTINGS index_granularity = 8192\n        SQL\n      end\n\n      it 'works' do\n        expect(ClickHouse::Util::Pretty.squish(schema)).to eq(ClickHouse::Util::Pretty.squish(expectation))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/array_spec.rb",
    "content": "RSpec.describe ClickHouse::Type::ArrayType do\n  subject do\n    ClickHouse.connection\n  end\n\n  describe 'cast' do\n    context 'many flat' do\n      before do\n        subject.execute <<~SQL\n          CREATE TABLE rspec(\n              a Array(DateTime),\n              b Array(Nullable(DateTime)),\n              c Array(DateTime64(3)),\n              d Array(Nullable(DateTime64(3))),\n              e Array(DateTime64(3, 'UTC')),\n              f Array(Nullable(DateTime64(3, 'UTC'))),\n              g Array(Decimal(10,2)),\n              h Array(String)\n           ) ENGINE Memory\n        SQL\n\n        subject.execute <<~SQL\n          insert into rspec values (\n            array(now()),\n            array(now()),\n            array(now()),\n            array(now()),\n            array(now()),\n            array(now()),\n            array(5.99),\n            array('foo')\n          );\n        SQL\n      end\n\n      it 'works' do\n        got = subject.select_one('SELECT * FROM rspec')\n        expect(got.fetch('a').first).to be_a(Time)\n        expect(got.fetch('b').first).to be_a(Time)\n        expect(got.fetch('c').first).to be_a(Time)\n        expect(got.fetch('d').first).to be_a(Time)\n        expect(got.fetch('e').first).to be_a(Time)\n        expect(got.fetch('f').first).to be_a(Time)\n        expect(got.fetch('g').first).to be_a(BigDecimal)\n        expect(got.fetch('h')).to eq(['foo'])\n      end\n    end\n\n    context 'many nested' do\n      before do\n        subject.execute <<~SQL\n          CREATE TABLE rspec(\n              a Array(Array(DateTime)),\n              b Array(Array(Nullable(DateTime))),\n              c Array(Array(Array(DateTime64(3)))),\n              d Array(Array(Array(Array(Nullable(DateTime64(3)))))),\n              e Array(Array(Array(Array(Array(DateTime64(3, 'UTC')))))),\n              f Array(Array(Array(Array(Array(Array(Nullable(DateTime64(3, 'UTC')))))))),\n              g Array(Array(Array(Array(Array(Array(Array(Decimal(10,2))))))))\n           ) ENGINE TinyLog\n        SQL\n\n        subject.execute <<~SQL\n          insert into rspec values (\n            array(array(now())),\n            array(array((now()))),\n            array(array(array(((now()))))),\n            array(array(array(array((((now()))))))),\n            array(array(array(array(array((((now())))))))),\n            array(array(array(array(array(array((((now()))))))))),\n            array(array(array(array(array(array(array((((5.99))))))))))\n          );\n        SQL\n      end\n\n      it 'works' do\n        got = subject.select_one('SELECT * FROM rspec')\n        expect(got.fetch('a').dig(0, 0)).to be_a(Time)\n        expect(got.fetch('b').dig(0, 0)).to be_a(Time)\n        expect(got.fetch('c').dig(0, 0, 0)).to be_a(Time)\n        expect(got.fetch('d').dig(0, 0, 0, 0)).to be_a(Time)\n        expect(got.fetch('e').dig(0, 0, 0, 0, 0)).to be_a(Time)\n        expect(got.fetch('f').dig(0, 0, 0, 0, 0, 0)).to be_a(Time)\n        expect(got.fetch('g').dig(0, 0, 0, 0, 0, 0, 0)).to be_a(BigDecimal)\n      end\n    end\n  end\n\n  describe 'serialize' do\n    before do\n      subject.execute <<~SQL\n          CREATE TABLE rspec(\n              a Array(Bool),\n              b Array(DateTime64(9, 'Europe/Kyiv')),\n              c Array(Array(UInt8)),\n              d Array(Array(Array(Nullable(UInt8)))),\n              e Array(String),          \n           ) ENGINE Memory\n      SQL\n    end\n\n    let(:row) do\n      {\n        'a' => [true],\n        'b' => [Time.find_zone(\"Europe/Kyiv\").now],\n        'c' => [[1]],\n        'd' => [[[nil]]],\n        'e' => ['foo']\n      }\n    end\n\n    it 'works' do\n      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))\n      got = subject.select_one('SELECT * FROM rspec')\n\n      expect(got.fetch('a')).to eq(row.fetch('a'))\n      expect(got.fetch('b')).to eq(row.fetch('b'))\n      expect(got.fetch('c')).to eq(row.fetch('c'))\n      expect(got.fetch('d')).to eq(row.fetch('d'))\n      expect(got.fetch('e')).to eq(row.fetch('e'))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/boolean_type_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::BooleanType do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n          a Boolean,\n          b Boolean,\n          c Boolean,\n          d Boolean,\n          e Nullable(Boolean)\n       ) ENGINE Memory\n    SQL\n  end\n\n  describe 'cast' do\n    before do\n      subject.execute <<~SQL\n        INSERT INTO rspec VALUES (\n          1,\n          0,\n          true,\n          false,\n          NULL\n        );\n      SQL\n    end\n\n    it 'works' do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.fetch('a')).to be_a(TrueClass)\n      expect(got.fetch('b')).to be_a(FalseClass)\n      expect(got.fetch('c')).to be_a(TrueClass)\n      expect(got.fetch('b')).to be_a(FalseClass)\n      expect(got.fetch('e')).to be_a(NilClass)\n    end\n  end\n\n  describe 'serialize' do\n    let(:row) do\n      {\n        'a' => true,\n        'b' => false,\n        'c' => 1,\n        'd' => 0,\n        'e' => nil\n      }\n    end\n\n    it 'works' do\n      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))\n      got = subject.select_one('SELECT * FROM rspec')\n\n      expect(got.fetch('a')).to be_a(TrueClass)\n      expect(got.fetch('b')).to be_a(FalseClass)\n      expect(got.fetch('c')).to be_a(TrueClass)\n      expect(got.fetch('b')).to be_a(FalseClass)\n      expect(got.fetch('e')).to be_a(NilClass)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/date_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::IPType do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n          a Date,\n          b Nullable(Date)\n       ) ENGINE Memory\n    SQL\n  end\n\n  describe 'cast' do\n    before do\n      subject.execute <<~SQL\n        INSERT INTO rspec VALUES (\n          '2022-01-02',\n          NULL\n        );\n      SQL\n    end\n\n    it 'works' do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.fetch('a')).to eq(Date.new(2022, 1, 2))\n      expect(got.fetch('b')).to eq(nil)\n    end\n  end\n\n  describe 'serialize' do\n    let(:row) do\n      {\n        'a' => Date.new(2022, 1, 2),\n        'b' => nil\n      }\n    end\n\n    it 'works' do\n      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))\n      got = subject.select_one('SELECT * FROM rspec')\n\n      expect(got.fetch('a')).to eq(row.fetch('a'))\n      expect(got.fetch('b')).to eq(row.fetch('b'))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/date_time64_spec.rb",
    "content": "RSpec.describe ClickHouse::Type::DateTime64Type do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n          a DateTime64(0),\n          b DateTime64(9, 'Europe/Kyiv'),\n          c Nullable(DateTime64(9)),\n          d Nullable(DateTime64(9, 'Europe/Kyiv')),\n          e Nullable(DateTime64(9)),\n       ) ENGINE Memory\n    SQL\n  end\n\n  describe 'cast' do\n    before do\n      subject.execute <<~SQL\n        INSERT INTO rspec values (\n          now(),\n          now(),\n          now(),\n          now(),\n          null\n        );\n      SQL\n    end\n\n    it 'works' do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.fetch('a')).to be_a(Time)\n      expect(got.fetch('a')).to have_attributes(zone: Time.now.zone)\n\n      expect(got.fetch('b')).to be_a(Time)\n      expect(got.fetch('b')).to have_attributes(zone: Time.find_zone('Europe/Kyiv').tzinfo.abbr)\n\n      expect(got.fetch('c')).to be_a(Time)\n      expect(got.fetch('d')).to be_a(Time)\n      expect(got.fetch('d')).to have_attributes(zone: Time.find_zone('Europe/Kyiv').tzinfo.abbr)\n\n      expect(got.fetch('e')).to be_a(NilClass)\n    end\n  end\n\n  describe 'serialize' do\n    let(:row) do\n      {\n        'a' => Time.now.round,\n        'b' => Time.find_zone(\"Europe/Kyiv\").now,\n        'c' => Time.now,\n        'd' => nil,\n        'e' => nil,\n      }\n    end\n\n    it 'works' do\n      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))\n      got = subject.select_one('SELECT * FROM rspec')\n\n      expect(got.fetch('a')).to eq(row.fetch('a'))\n      expect(got.fetch('b')).to eq(row.fetch('b'))\n      expect(got.fetch('c')).to eq(row.fetch('c'))\n      expect(got.fetch('d')).to eq(row.fetch('d'))\n      expect(got.fetch('e')).to eq(row.fetch('e'))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/date_time_spec.rb",
    "content": "RSpec.describe ClickHouse::Type::DateTimeType do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n          a DateTime,\n          b DateTime('Europe/Kyiv'),\n          c Nullable(DateTime),\n          d Nullable(DateTime('Europe/Kyiv')),\n          e Nullable(DateTime),\n       ) ENGINE Memory\n    SQL\n  end\n\n  describe 'cast' do\n    before do\n      subject.execute <<~SQL\n        INSERT INTO rspec VALUES (\n          now(),\n          now(),\n          now(),\n          now(),\n          null\n        );\n      SQL\n    end\n\n    it 'works' do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.fetch('a')).to be_a(Time)\n      expect(got.fetch('a')).to have_attributes(zone: Time.now.zone)\n\n      expect(got.fetch('b')).to be_a(Time)\n      expect(got.fetch('b')).to have_attributes(zone: Time.find_zone('Europe/Kyiv').tzinfo.abbr)\n\n      expect(got.fetch('c')).to be_a(Time)\n      expect(got.fetch('d')).to be_a(Time)\n      expect(got.fetch('d')).to have_attributes(zone: Time.find_zone('Europe/Kyiv').tzinfo.abbr)\n\n      expect(got.fetch('e')).to be_a(NilClass)\n    end\n  end\n\n  describe 'serialize' do\n    let(:row) do\n      {\n        'a' => Time.now.round,\n        'b' => Time.find_zone(\"Europe/Kyiv\").now.round,\n        'c' => Time.now.round,\n        'd' => nil,\n        'e' => nil,\n      }\n    end\n\n    it 'works' do\n      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))\n      got = subject.select_one('SELECT * FROM rspec')\n\n      expect(got.fetch('a')).to eq(row.fetch('a'))\n      expect(got.fetch('b')).to eq(row.fetch('b'))\n      expect(got.fetch('c')).to eq(row.fetch('c'))\n      expect(got.fetch('d')).to eq(nil)\n      expect(got.fetch('e')).to eq(nil)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/decimal_spec.rb",
    "content": "RSpec.describe ClickHouse::Type::DecimalType do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n          a Decimal(10,10),\n          b Decimal32(1),\n          c Decimal64(10),\n          d Decimal128(20),\n          e Nullable(Decimal256(30))\n       ) ENGINE Memory\n    SQL\n  end\n\n  describe 'cast' do\n    before do\n      subject.execute <<~SQL\n        INSERT INTO rspec VALUES (\n          0.1,\n          1/3,\n          1/3,\n          1/3,\n          1/3\n        );\n      SQL\n    end\n\n    it 'works' do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.fetch('a')).to be_a(BigDecimal)\n      expect(got.fetch('b')).to be_a(BigDecimal)\n      expect(got.fetch('c')).to be_a(BigDecimal)\n      expect(got.fetch('d')).to be_a(BigDecimal)\n      expect(got.fetch('e')).to be_a(BigDecimal)\n    end\n\n    it 'works with correct precision', if: ruby_version_gt('2.8') do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.fetch('a').precision).to eq(1)\n      expect(got.fetch('b').precision).to eq(1)\n      expect(got.fetch('c').precision).to eq(10)\n      expect(got.fetch('d').precision).to eq(20)\n      expect(got.fetch('e').precision).to eq(30)\n    end\n  end\n\n  describe 'serialize' do\n    let(:row) do\n      {\n        'a' => \"0.1\",\n        'b' => BigDecimal(1),\n        'c' => 1.fdiv(3),\n        'd' => 1,\n        'e' => nil\n      }\n    end\n\n    it 'works' do\n      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))\n      got = subject.select_one('SELECT * FROM rspec')\n\n      expect(got.fetch('a')).to eq(BigDecimal(\"0.1\"))\n      expect(got.fetch('b')).to eq(BigDecimal(1))\n      expect(got.fetch('c')).to eq(BigDecimal(1.fdiv(3), 10))\n      expect(got.fetch('d')).to eq(BigDecimal(1))\n      expect(got.fetch('e')).to eq(nil)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/enum_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe 'Enum' do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n         a Enum('foo' = 1, 'bar' = 2),\n         b Nullable(Enum('foo' = 1, 'bar' = 2))\n       ) ENGINE Memory\n    SQL\n  end\n\n  describe 'cast' do\n    before do\n      subject.execute <<~SQL\n        INSERT INTO rspec VALUES (\n          1, \n          NULL\n        )\n      SQL\n    end\n\n    it 'works' do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.fetch('a')).to eq('foo')\n      expect(got.fetch('b')).to eq(nil)\n    end\n  end\n\n  describe 'serialize' do\n    let(:row) do\n      {\n        'a' => 'foo',\n        'b' => nil,\n      }\n    end\n\n    it 'works' do\n      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))\n      got = subject.select_one('SELECT * FROM rspec')\n\n      expect(got.fetch('a')).to eq('foo')\n      expect(got.fetch('b')).to eq(nil)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/float_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::FloatType do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n          a Float32,\n          b Float64,        \n          c Nullable(Float64)\n       ) ENGINE Memory\n    SQL\n  end\n\n  describe 'cast' do\n    before do\n      subject.execute <<~SQL\n        INSERT INTO rspec VALUES (\n            1.1,\n            2.2,\n            NULL\n        );\n      SQL\n    end\n\n    it 'works' do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.fetch('a')).to eq(1.1)\n      expect(got.fetch('b')).to eq(2.2)\n      expect(got.fetch('c')).to eq(nil)\n    end\n  end\n\n  describe 'serialize' do\n    let(:row) do\n      {\n        'a' => 1.1,\n        'b' => 2.2,\n        'c' => nil,\n      }\n    end\n\n    it 'works' do\n      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))\n      got = subject.select_one('SELECT * FROM rspec')\n\n      expect(got.fetch('a')).to eq(row.fetch('a'))\n      expect(got.fetch('b')).to eq(row.fetch('b'))\n      expect(got.fetch('c')).to eq(row.fetch('c'))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/formats.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Extend::ConnectionSelective do\n  subject do\n    ClickHouse.connection\n  end\n\n  context 'when RowBinary' do\n    let(:query) do\n      'SELECT 1 FORMAT RowBinary'\n    end\n\n    it '#select_one' do\n      got = subject.select_one(query)\n      expect(got).to eq(\"\\u0001\")\n    end\n\n    it '#select_value' do\n      got = subject.select_value(query)\n      expect(got).to eq(\"\\u0001\")\n    end\n\n    it '#summary' do\n      got = subject.select_all(query)\n      expect(got.summary.read_rows).to eq(1)\n    end\n\n    it '#types' do\n      got = subject.select_all(query)\n      expect(got.types).to eq([])\n    end\n  end\n\n  context 'when CSV' do\n    let(:query) do\n      'SELECT 1 FORMAT CSV'\n    end\n\n    it '#select_one' do\n      got = subject.select_one(query)\n      expect(got).to eq(['1'])\n    end\n\n    it '#select_value' do\n      got = subject.select_value(query)\n      expect(got).to eq('1')\n    end\n\n    it '#summary' do\n      got = subject.select_all(query)\n      expect(got.summary.read_rows).to eq(1)\n    end\n\n    it '#types' do\n      got = subject.select_all(query)\n      expect(got.types).to eq([])\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/function_spec.rb",
    "content": "RSpec.describe 'Functions' do\n  subject do\n    ClickHouse.connection\n  end\n\n  let(:expectations) do \n    {\n      'select NOW()' => Time,\n      'select 1 + 1' => Integer,\n      'select 1 * 1.0' => Float,\n      'select empty([])' => Integer,\n      'select 1 > 0' => Integer,\n    }\n  end\n\n  it 'works' do\n    expectations.each do |query, klass|\n      expect(subject.select_value(query)).to be_a(klass)\n    end  \n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/integer_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::IntegerType do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n          a UInt8,\n          b UInt16,\n          c UInt32, \n          d UInt64,\n          e Int8,\n          f Int16,\n          g Int32,\n          h Int64,\n          k Nullable(Int64)\n       ) ENGINE Memory\n    SQL\n  end\n\n  describe 'cast' do\n    before do\n      subject.execute <<~SQL\n        INSERT INTO rspec VALUES (\n            1,\n            2,\n            3,\n            4,\n            5,\n            6,\n            7,\n            8,\n            NULL\n        );\n      SQL\n    end\n\n    it 'works' do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.fetch('a')).to eq(1)\n      expect(got.fetch('b')).to eq(2)\n      expect(got.fetch('c')).to eq(3)\n      expect(got.fetch('d')).to eq(4)\n      expect(got.fetch('e')).to eq(5)\n      expect(got.fetch('f')).to eq(6)\n      expect(got.fetch('g')).to eq(7)\n      expect(got.fetch('h')).to eq(8)\n      expect(got.fetch('k')).to eq(nil)\n    end\n  end\n\n  describe 'serialize' do\n    let(:row) do\n      {\n        'a' => 1,\n        'b' => 2,\n        'c' => 3,\n        'd' => 4,\n        'e' => 5,\n        'f' => 6,\n        'g' => 7,\n        'h' => 8,\n        'k' => nil,\n      }\n    end\n\n    it 'works' do\n      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))\n      got = subject.select_one('SELECT * FROM rspec')\n\n      expect(got.fetch('a')).to eq(row.fetch('a'))\n      expect(got.fetch('b')).to eq(row.fetch('b'))\n      expect(got.fetch('c')).to eq(row.fetch('c'))\n      expect(got.fetch('d')).to eq(row.fetch('d'))\n      expect(got.fetch('e')).to eq(row.fetch('e'))\n      expect(got.fetch('f')).to eq(row.fetch('f'))\n      expect(got.fetch('g')).to eq(row.fetch('g'))\n      expect(got.fetch('h')).to eq(row.fetch('h'))\n      expect(got.fetch('k')).to eq(row.fetch('k'))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/ip_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::IPType do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n          a IPv4,\n          b Nullable(IPv4),\n          c IPv6,\n          d Nullable(IPv6)\n       ) ENGINE Memory\n    SQL\n  end\n\n  describe 'cast' do\n    before do\n      subject.execute <<~SQL\n        INSERT INTO rspec VALUES (\n          '127.0.0.1',\n          '127.0.0.1',\n          '::1',\n          NULL\n        );\n      SQL\n    end\n\n    it 'works' do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.fetch('a')).to eq(IPAddr.new('127.0.0.1'))\n      expect(got.fetch('b')).to eq(IPAddr.new('127.0.0.1'))\n      expect(got.fetch('c')).to eq(IPAddr.new('::1'))\n      expect(got.fetch('d')).to eq(nil)\n    end\n  end\n\n  describe 'serialize' do\n    let(:row) do\n      {\n        'a' => IPAddr.new('127.0.0.1'),\n        'b' => '127.0.0.1', # as string\n        'c' => IPAddr.new('::1'),\n        'd' => nil\n      }\n    end\n\n    it 'works' do\n      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))\n      got = subject.select_one('SELECT * FROM rspec')\n\n      expect(got.fetch('a')).to eq(row.fetch('a'))\n      expect(got.fetch('b')).to eq(IPAddr.new(row.fetch('b')))\n      expect(got.fetch('c')).to eq(row.fetch('c'))\n      expect(got.fetch('d')).to eq(row.fetch('d'))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/loggin_spec.rb",
    "content": "RSpec.describe ClickHouse::Middleware::Logging do\n  subject do\n    ClickHouse::Connection.new(ClickHouse.config.clone.assign(logger: logger))\n  end\n\n  let(:out) do\n    StringIO.new\n  end\n\n  let(:logger) do\n    Logger.new(out)\n  end\n\n  context 'when POST' do\n    it 'works' do\n      subject.execute('SELECT 1')\n      expect(out.string).to match(/Total: \\d/)\n      expect(out.string).to include('SELECT 1;')\n      expect(out.string).to include('Read: 1 rows')\n      expect(out.string).to include('Written: 0 rows')\n    end\n  end\n\n  context 'when GET' do\n    it 'works' do\n      subject.select_all('SELECT 1')\n      expect(out.string).to match(/Total: \\d/)\n      expect(out.string).to include('SELECT 1;')\n      expect(out.string).to include('Read: 1 rows')\n      expect(out.string).to include('Written: 0 rows')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/low_cardinality_spec.rb",
    "content": "RSpec.describe ClickHouse::Type::LowCardinalityType do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n          a LowCardinality(DateTime),\n          b LowCardinality(DateTime('Europe/Kyiv')),\n          c LowCardinality(Nullable(String))\n       ) ENGINE Memory\n    SQL\n  end\n\n  describe 'cast' do\n    before do\n      subject.execute <<~SQL\n        INSERT INTO rspec VALUES (\n          now(),\n          now(),\n          null\n        );\n      SQL\n    end\n\n    it 'works' do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.fetch('a')).to be_a(Time)\n      expect(got.fetch('a')).to have_attributes(zone: Time.now.zone)\n\n      expect(got.fetch('b')).to be_a(Time)\n      expect(got.fetch('b')).to have_attributes(zone: Time.find_zone('Europe/Kyiv').tzinfo.abbr)\n\n      expect(got.fetch('c')).to be_a(NilClass)\n    end\n  end\n\n  describe 'serialize' do\n    let(:row) do\n      {\n        'a' => Time.now,\n        'b' => Time.now,\n        'c' => nil,\n      }\n    end\n\n    it 'works' do\n      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))\n      got = subject.select_one('SELECT * FROM rspec')\n\n      expect(got.fetch('a')).to be_a(Time)\n      expect(got.fetch('b')).to be_a(Time)\n      expect(got.fetch('c')).to be_a(NilClass)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/map_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::MapType do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n          a Map(LowCardinality(String), Array(DateTime('Europe/Kyiv'))),\n          b Map(Int8, IPv4)\n       ) ENGINE Memory\n    SQL\n  end\n\n  describe 'cast' do\n    before do\n      subject.execute <<~SQL\n        INSERT INTO rspec VALUES (\n          {'foo': ['2019-01-01']}, \n          {1: '127.0.0.1'}\n        )\n      SQL\n    end\n\n    it 'works' do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.dig('a', 'foo')).to eq([Time.find_zone('Europe/Kyiv').parse('2019-01-01')])\n      expect(got.dig('b', 1)).to eq(IPAddr.new('127.0.0.1'))\n    end\n  end\n\n  describe 'serialize' do\n    let(:row) do\n      {\n        'a' => {'foo' => [Time.find_zone('Europe/Kyiv').now.round]},\n        'b' => { 1 => IPAddr.new('127.0.0.1')},\n      }\n    end\n\n    it 'works' do\n      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))\n      got = subject.select_one('SELECT * FROM rspec')\n\n      expect(got.fetch('a')).to eq(row.fetch('a'))\n      expect(got.fetch('b')).to eq(row.fetch('b'))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/nested_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe 'Nested' do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n        json Nested(\n          a Date\n        ) \n       ) ENGINE Memory\n    SQL\n  end\n\n  describe 'cast' do\n    before do\n      subject.execute <<~SQL\n        INSERT INTO rspec VALUES (\n          (['2022-01-01']) \n        )\n      SQL\n    end\n\n    it 'works' do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.fetch('json.a')).to eq([Date.new(2022, 1, 1)])\n    end\n  end\n\n  describe 'serialize' do\n    let(:row) do\n      {\n        'json.a' => [Date.new(2022, 1, 2)]\n      }\n    end\n\n    it 'works' do\n      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))\n      got = subject.select_one('SELECT * FROM rspec')\n\n      expect(got.fetch('json.a')).to eq(row.fetch('json.a'))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/string_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::StringType do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n          a String,\n          b FixedString(2),\n          c UUID, \n          d Nullable(UUID)\n       ) ENGINE Memory\n    SQL\n  end\n\n  describe 'cast' do\n    before do\n      subject.execute <<~SQL\n        INSERT INTO rspec VALUES (\n            'x',\n            'y',\n            'da70495b-1ff7-49e5-8feb-d657bd4ea1ea',\n            NULL\n        );\n      SQL\n    end\n\n    it 'works' do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.fetch('a')).to eq(\"x\")\n      expect(got.fetch('b')).to eq(\"y\\u0000\")\n      expect(got.fetch('c')).to eq(\"da70495b-1ff7-49e5-8feb-d657bd4ea1ea\")\n      expect(got.fetch('d')).to eq(nil)\n    end\n  end\n\n  describe 'serialize' do\n    let(:row) do\n      {\n        'a' => 'foo',\n        'b' => 'xe',\n        'c' => \"da70495b-1ff7-49e5-8feb-d657bd4ea1ea\",\n        'd' => nil\n      }\n    end\n\n    it 'works' do\n      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))\n      got = subject.select_one('SELECT * FROM rspec')\n\n      expect(got.fetch('a')).to eq(row.fetch('a'))\n      expect(got.fetch('b')).to eq(row.fetch('b'))\n      expect(got.fetch('c')).to eq(row.fetch('c'))\n      expect(got.fetch('d')).to eq(row.fetch('d'))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/symbolize_keys_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Config do\n  subject do\n    ClickHouse::Connection.new(ClickHouse.config.clone.assign(symbolize_keys: true))\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n          a String,\n      ) ENGINE Memory\n    SQL\n  end\n\n  describe 'cast' do\n    before do\n      subject.execute <<~SQL\n        INSERT INTO rspec VALUES (\n           'foo'\n        );\n      SQL\n    end\n\n    it 'works' do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got).to eq({a: \"foo\"})\n\n      got = subject.select_value('SELECT * FROM rspec')\n      expect(got).to eq(\"foo\")\n\n      got = subject.select_all('SELECT * FROM rspec')\n      expect(got.meta).to eq([{:name=>\"a\", :type=>\"String\"}])\n      expect(got.statistics).to include(rows_read: 1)\n    end\n  end\n\n  describe 'serialize' do\n    let(:row) do\n      {\n        a: 'foo'\n      }\n    end\n\n    it 'works' do\n      subject.insert_rows('rspec', subject.table_schema('rspec').serialize_one(row))\n\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.fetch(:a)).to eq(row.fetch(:a))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/table_schema_spec.rb",
    "content": "RSpec.describe ClickHouse::Response::ResultSet do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n          a Boolean,\n          b Array(Nullable(String))\n       ) ENGINE Memory\n    SQL\n  end\n\n  let(:schema) do\n    subject.table_schema('rspec')\n  end\n\n  describe '#types' do\n    it 'works' do\n      expect(schema.types).to have_key('a')\n      expect(schema.types).to have_key('b')\n    end\n  end\n\n  describe '#serialize_column' do\n    it 'works' do\n      expect(schema.serialize_column('a', true)).to eq(1)\n      expect(schema.serialize_column('b', [])).to eq([])\n    end\n\n    it 'errors if column missing' do\n      expect { schema.serialize_column('foo', 'bar') }.to raise_error(ClickHouse::SerializeError)\n    end\n\n    it 'errors if value has improper type' do\n      expect { schema.serialize_column('b', nil) }.to raise_error(ClickHouse::SerializeError)\n    end\n  end\n\n  describe '#serialize_one' do\n    it 'works' do\n      expect(schema.serialize_one({'a' => true, 'b' => ['foo']})).to eq({'a' => 1, 'b' => ['foo']})\n    end\n  end\n\n  describe '#serialize' do\n    let(:row) do\n      {'a' => true, 'b' => ['foo']}\n    end\n\n    let(:expectation) do\n      {'a' => 1, 'b' => ['foo']}\n    end\n\n    context 'when Hash' do\n      it 'works' do\n        expect(schema.serialize(row)).to eq(expectation)\n      end\n    end\n\n    context 'when Array' do\n      it 'works' do\n        expect(schema.serialize([row])).to eq([expectation])\n      end\n    end\n\n    context 'when other' do\n      it 'errors' do\n        expect { schema.serialize(nil) }.to raise_error(ArgumentError)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/integration/tuple_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::TupleType do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.execute <<~SQL\n      CREATE TABLE rspec(\n          a Tuple(IPv4, Nullable(Date), Nullable(String))\n       ) ENGINE Memory\n    SQL\n  end\n\n  describe 'cast' do\n    before do\n      subject.execute <<~SQL\n        INSERT INTO rspec VALUES (\n          ('127.0.0.1', '2022-01-02', NULL)\n        );\n      SQL\n    end\n\n    let(:expectation) do\n      [\n        IPAddr.new('127.0.0.1'),\n        Date.new(2022, 1, 2),\n        nil\n      ]\n    end\n\n    it 'cast type' do\n      got = subject.select_one('SELECT * FROM rspec')\n      expect(got.fetch('a')).to eq(expectation)\n    end\n  end\n\n  describe 'serialize' do\n    let(:row) do\n      {\n        'a' =>  [\n          IPAddr.new('127.0.0.1'),\n          Date.new(2022, 1, 1),\n          nil\n        ],\n      }\n    end\n\n    it 'works' do\n      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))\n      got = subject.select_one('SELECT * FROM rspec')\n\n      expect(got.fetch('a')).to eq(row.fetch('a'))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/response/factory_spec.rb",
    "content": "RSpec.describe ClickHouse::Response::Factory do\n  subject do\n    ClickHouse.connection\n  end\n\n  describe 'WITH totals modifier' do\n    context 'when blank' do\n      let(:response) do\n        subject.select_all('SELECT 1')\n      end\n\n      it 'is empty' do\n        expect(response.totals).to eq(nil)\n      end\n    end\n\n    context 'when exists' do\n      let(:response) do\n        subject.select_all('SELECT SUM(1) AS s WITH TOTALS')\n      end\n\n      it 'is present' do\n        expect(response.totals).to eq({ 's' => '1' })\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/type/date_time64_spec.rb",
    "content": "RSpec.describe ClickHouse::Type::DateTime64Type do\n  let(:precisions) do \n    (0..9).to_a\n  end\n\n  describe '#serialize' do\n    let(:time) do\n      Time.new(2019, 1, 1, 9, 5, 6)\n    end\n\n    it 'works' do\n      precisions.each do |precision|        \n        tail = \".\" + \"0\" * precision if precision > 0\n        expect(subject.serialize(time, precision)).to eq(\"2019-01-01 09:05:06#{tail}\")\n      end\n    end\n  end\n\n  describe '#cast' do\n    context 'when zone is empty' do\n      let(:time) do\n        Time.new(2019, 1, 1, 9, 5, 6)\n      end\n\n      it 'works' do\n        expect(subject.cast('2019-01-01 09:05:06.0000')).to eq(time)\n      end\n    end\n\n    context 'when zone exists' do\n      let(:time) do\n        Time.new(2019, 1, 1, 9, 5, 6, Time.find_zone('Europe/Kyiv'))\n      end\n\n      it 'works' do\n        expect(subject.cast('2019-01-01 09:05:06', 0, 'Europe/Kyiv').to_s).to eq(time.to_s)\n        expect(subject.cast('2019-01-01 09:05:06.00', 9, 'Europe/Kyiv').to_s).to eq(time.to_s)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/type/date_time_type_spec.rb",
    "content": "RSpec.describe ClickHouse::Type::DateTimeType do\n  describe '#serialize' do\n    let(:time) do\n      Time.new(2019, 1, 1, 9, 5, 6)\n    end\n\n    it 'works' do\n      expect(subject.serialize(time)).to eq('2019-01-01 09:05:06')\n    end\n  end\n\n  describe '#cast' do\n    context 'when zone is empty' do\n      let(:time) do\n        Time.new(2019, 1, 1, 9, 5, 6)\n      end\n\n      it 'works' do\n        expect(subject.cast('2019-01-01 09:05:06')).to eq(time)\n      end\n    end\n\n    context 'when zone exists' do\n      let(:time) do\n        Time.new(2019, 1, 1, 9, 5, 6, Time.find_zone('Europe/Kyiv'))\n      end\n\n      it 'works' do\n        expect(subject.cast('2019-01-01 09:05:06', 'Europe/Kyiv')).to eq(time)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/type/decimal_type_spec.rb",
    "content": "RSpec.describe ClickHouse::Type::DecimalType do\n  describe '#casr' do\n    context 'when String' do\n      it 'works' do\n        expect(subject.cast(\"1.0\")).to eq(BigDecimal(1.0, 1))\n      end\n    end\n\n    context 'when BigDecimal' do\n      it 'works' do\n        expect(subject.cast(BigDecimal(1))).to eq(BigDecimal(1))\n      end\n    end\n\n    context 'when Float' do\n      it 'works' do\n        expect(subject.cast(1.0, 1)).to eq(BigDecimal(1.0, 1))\n        expect(subject.cast(1.0)).to eq(BigDecimal(1.0, ClickHouse::Type::DecimalType::MAXIMUM))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/type/fixed_string_type_spec.rb",
    "content": "RSpec.describe ClickHouse::Type::FixedStringType do\n  describe '#serialize' do\n    def target(value, limit = nil)\n      described_class.new.serialize(value, limit)\n    end\n\n    it 'works' do\n      expect(target(nil)).to eq(nil)\n      expect(target('foo bar')).to eq('foo bar')\n      expect(target('foo bar', 1)).to eq('f')\n      expect(target('foo bar', 2)).to eq('fo')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/type/float_type_spec.rb",
    "content": "RSpec.describe ClickHouse::Type::FloatType do\n  describe '#serialize' do\n    it 'works' do\n      expect(subject.serialize(5)).to be_a(Float)\n      expect(subject.serialize(5)).to eq(5.0)\n      expect(subject.serialize(5.0)).to eq(5.0)\n      expect(subject.serialize(nil)).to eq(nil)\n    end\n  end\n\n  describe '#cast' do\n    it 'works' do\n      expect(subject.cast(5)).to be_a(Float)\n      expect(subject.cast(5)).to eq(5.0)\n      expect(subject.cast(5.0)).to eq(5.0)\n      expect(subject.cast(nil)).to eq(nil)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/type/ip_type_spec.rb",
    "content": "RSpec.describe ClickHouse::Type::IPType do\n  describe '#cast' do\n    let(:ip) do\n      IPAddr.new('127.0.0.1')\n    end\n\n    it 'works' do\n      expect(subject.cast(String(ip))).to eq(ip)\n    end\n  end\n\n  describe '#serialize' do\n    let(:ip) do\n      IPAddr.new('127.0.0.1')\n    end\n\n    it 'works' do\n      expect(subject.serialize(ip)).to eq('127.0.0.1')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/click_house/util/pretty_spec.rb",
    "content": "RSpec.describe ClickHouse::Util::Pretty do\n  describe '#size' do\n    let(:expectation) do\n      {\n        0 => '0B',\n        100 => '100.0B',\n        1456 => '1.4KiB',\n        1024000 * 2 => '2.0MiB',\n        10737418240 => '10.0GiB',\n        10737418240 * 1_000 => '9.8TiB',\n      }\n    end\n\n    it 'works' do\n      expectation.each do |bytes, pretty|\n        expect(described_class.size(bytes)).to eq(pretty)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/oj_helper.rb",
    "content": "# frozen_string_literal: tru\n\nRSpec.configure do |config|\n  config.before(:each) do\n    ClickHouse.config do |c|\n      c.json_parser = ClickHouse::Middleware::ParseJsonOj\n      c.json_serializer = ClickHouse::Serializer::JsonOjSerializer\n    end\n  end\nend\n"
  },
  {
    "path": "spec/spec_helper.rb",
    "content": "# frozen_string_literal: true\n\n$ROOT_PATH = File.expand_path('../../', __FILE__).freeze\n\nrequire 'bundler/setup'\nrequire 'click_house'\nrequire 'pry'\n\nDir[File.join($ROOT_PATH, 'spec', 'support', '*.rb')].each { |f| require f }\n\nClickHouse.config do |config|\n  config.logger = Logger.new('log/test.log', level: Logger::DEBUG)\n  config.database = 'click_house_rspec'\n  config.url = 'http://localhost:8123?allow_suspicious_low_cardinality_types=1&output_format_arrow_low_cardinality_as_dictionary=1'\nend\n\nRSpec.configure do |config|\n  config.example_status_persistence_file_path = '.rspec_status'\n  config.disable_monkey_patching!\n  config.filter_run :focus\n  config.run_all_when_everything_filtered = true\n  config.order = :random\n  Kernel.srand config.seed\n\n  config.expect_with :rspec do |c|\n    c.syntax = :expect\n  end\nend\n"
  },
  {
    "path": "spec/support/database_cleaner.rb",
    "content": "SYSTEM_DATABASES = %w[default system _temporary_and_external_tables]\n\nRSpec.configure do |config|\n  config.around(:each) do |example|\n    ClickHouse.connection.create_database(ClickHouse.config.database, if_not_exists: true)\n\n    example.run\n\n    ClickHouse.connection.databases.each do |database|\n      next if SYSTEM_DATABASES.include?(database)\n\n      ClickHouse.connection.drop_database(database, if_exists: true)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/reset_connection.rb",
    "content": "# frozen_string_literal: tru\n\nRSpec.configure do |config|\n  config.around(:each) do |example|\n    ClickHouse.connection = nil\n    example.run\n  end\nend\n"
  },
  {
    "path": "spec/support/ruby_version.rb",
    "content": "# frozen_string_literal: true\n\n# @return [Boolean]\n# @param version [String] like \"2.7\"\ndef ruby_version_gt(version)\n  Gem::Version.new(RUBY_VERSION) > Gem::Version.new(version)\nend\n\n# @return [Boolean]\n# @param version [String] like \"2.7\"\ndef ruby_version_lt(version)\n  Gem::Version.new(RUBY_VERSION) < Gem::Version.new(version)\nend\n"
  },
  {
    "path": "tmp/.keep",
    "content": "\n"
  }
]