Full Code of shlima/click_house for AI

master 663b72d42fbc cached
124 files
151.3 KB
45.2k tokens
360 symbols
1 requests
Download .txt
Repository: shlima/click_house
Branch: master
Commit: 663b72d42fbc
Files: 124
Total size: 151.3 KB

Directory structure:
gitextract_7a4m72cg/

├── .github/
│   └── workflows/
│       └── main.yml
├── .gitignore
├── .rspec
├── .rubocop.yml
├── CHANGELOG.md
├── Gemfile
├── Gemfile_faraday1
├── LICENCE.txt
├── Makefile
├── README.md
├── Rakefile
├── bin/
│   ├── console
│   ├── release.sh
│   └── setup
├── click_house.gemspec
├── docker-compose.yml
├── lib/
│   ├── click_house/
│   │   ├── ast/
│   │   │   ├── parser.rb
│   │   │   ├── statement.rb
│   │   │   └── ticker.rb
│   │   ├── ast.rb
│   │   ├── benchmark/
│   │   │   ├── casting.rb
│   │   │   └── map_join.rb
│   │   ├── config.rb
│   │   ├── connection.rb
│   │   ├── definition/
│   │   │   ├── column.rb
│   │   │   └── column_set.rb
│   │   ├── definition.rb
│   │   ├── errors.rb
│   │   ├── extend/
│   │   │   ├── configurable.rb
│   │   │   ├── connectible.rb
│   │   │   ├── connection_altering.rb
│   │   │   ├── connection_database.rb
│   │   │   ├── connection_explaining.rb
│   │   │   ├── connection_healthy.rb
│   │   │   ├── connection_inserting.rb
│   │   │   ├── connection_selective.rb
│   │   │   ├── connection_table.rb
│   │   │   └── type_definition.rb
│   │   ├── extend.rb
│   │   ├── middleware/
│   │   │   ├── logging.rb
│   │   │   ├── parse_csv.rb
│   │   │   ├── parse_json.rb
│   │   │   ├── parse_json_oj.rb
│   │   │   ├── raise_error.rb
│   │   │   ├── response_base.rb
│   │   │   └── summary_middleware.rb
│   │   ├── middleware.rb
│   │   ├── response/
│   │   │   ├── factory.rb
│   │   │   ├── result_set.rb
│   │   │   └── summary.rb
│   │   ├── response.rb
│   │   ├── serializer/
│   │   │   ├── base.rb
│   │   │   ├── json_oj_serializer.rb
│   │   │   └── json_serializer.rb
│   │   ├── serializer.rb
│   │   ├── type/
│   │   │   ├── array_type.rb
│   │   │   ├── base_type.rb
│   │   │   ├── boolean_type.rb
│   │   │   ├── date_time64_type.rb
│   │   │   ├── date_time_type.rb
│   │   │   ├── date_type.rb
│   │   │   ├── decimal_type.rb
│   │   │   ├── fixed_string_type.rb
│   │   │   ├── float_type.rb
│   │   │   ├── integer_type.rb
│   │   │   ├── ip_type.rb
│   │   │   ├── low_cardinality_type.rb
│   │   │   ├── map_type.rb
│   │   │   ├── nullable_type.rb
│   │   │   ├── string_type.rb
│   │   │   ├── tuple_type.rb
│   │   │   └── undefined_type.rb
│   │   ├── type.rb
│   │   ├── util/
│   │   │   ├── pretty.rb
│   │   │   └── statement.rb
│   │   ├── util.rb
│   │   └── version.rb
│   └── click_house.rb
├── log/
│   └── .keep
├── spec/
│   ├── click_house/
│   │   ├── ast/
│   │   │   └── parser_spec.rb
│   │   ├── config_spec.rb
│   │   ├── connection_spec.rb
│   │   ├── definition/
│   │   │   └── column_set_spec.rb
│   │   ├── extend/
│   │   │   ├── connection_altering_spec.rb
│   │   │   ├── connection_database_spec.rb
│   │   │   ├── connection_explaining_spec.rb
│   │   │   ├── connection_healthy_spec.rb
│   │   │   ├── connection_inserting_spec.rb
│   │   │   ├── connection_selective_spec.rb
│   │   │   └── connection_table_spec.rb
│   │   ├── integration/
│   │   │   ├── array_spec.rb
│   │   │   ├── boolean_type_spec.rb
│   │   │   ├── date_spec.rb
│   │   │   ├── date_time64_spec.rb
│   │   │   ├── date_time_spec.rb
│   │   │   ├── decimal_spec.rb
│   │   │   ├── enum_spec.rb
│   │   │   ├── float_spec.rb
│   │   │   ├── formats.rb
│   │   │   ├── function_spec.rb
│   │   │   ├── integer_spec.rb
│   │   │   ├── ip_spec.rb
│   │   │   ├── loggin_spec.rb
│   │   │   ├── low_cardinality_spec.rb
│   │   │   ├── map_spec.rb
│   │   │   ├── nested_spec.rb
│   │   │   ├── string_spec.rb
│   │   │   ├── symbolize_keys_spec.rb
│   │   │   ├── table_schema_spec.rb
│   │   │   └── tuple_spec.rb
│   │   ├── response/
│   │   │   └── factory_spec.rb
│   │   ├── type/
│   │   │   ├── date_time64_spec.rb
│   │   │   ├── date_time_type_spec.rb
│   │   │   ├── decimal_type_spec.rb
│   │   │   ├── fixed_string_type_spec.rb
│   │   │   ├── float_type_spec.rb
│   │   │   └── ip_type_spec.rb
│   │   └── util/
│   │       └── pretty_spec.rb
│   ├── oj_helper.rb
│   ├── spec_helper.rb
│   └── support/
│       ├── database_cleaner.rb
│       ├── reset_connection.rb
│       └── ruby_version.rb
└── tmp/
    └── .keep

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/main.yml
================================================
name: CI

on: [push, pull_request]

jobs:
  rspec:
    runs-on: ubuntu-latest

    services:
      clickhouse:
        image: clickhouse/clickhouse-server:22.9
        ports: 
          - 8123:8123

    strategy:
      matrix:
        ruby-version: [3.1, '3.0', 2.7]

    steps:
      - uses: actions/checkout@v2

      - name: Set up Ruby ${{ matrix.ruby-version }}
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: ${{ matrix.ruby-version }}

      - name: Setup v1
        run: make faraday1 bundle

      - name: Setup v2
        run: make faraday2 bundle

      - name: Run tests with faraday v.1 JSON
        run: make faraday1 rspec
      - name: Run tests with faraday v.2 JSON
        run: make faraday2 rspec
      - name: Run tests with faraday v.1 OJ
        run: make faraday1 oj rspec
      - name: Run tests with faraday v.2 OJ
        run: make faraday2 oj rspec

  rubocop:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Set up Ruby 2.7
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.7
          bundler-cache: true # 'bundle install' and cache

      - name: Run Rubocop
        run: bundle exec rubocop


================================================
FILE: .gitignore
================================================
/.bundle/
/.yardoc
/.idea/
/_yardoc/
/coverage/
/pkg/
/spec/reports/
/log/*
/tmp/*
/*.gem

!/log/.keep
!/tmp/.keep

# rspec failure tracking
.rspec_status


================================================
FILE: .rspec
================================================
--format documentation
--color
--require spec_helper


================================================
FILE: .rubocop.yml
================================================
require:
  - rubocop-performance

AllCops:
  AutoCorrect: false
  SuggestExtensions: false
  Exclude:
    - 'click_house.gemspec'
    - 'bin/*'
    - 'lib/click_house/benchmark/*'
    - 'spec/**/*'
    - 'vendor/**/*'
  TargetRubyVersion: 2.7
Bundler/OrderedGems:
  Enabled: false

# ============================== Gemspec ======================
Gemspec/DeprecatedAttributeAssignment:
  Enabled: true
Gemspec/RequireMFA: # new in 1.23
  Enabled: true

# =============================== Performance =======================
Performance/AncestorsInclude:
  Enabled: true
Performance/BigDecimalWithNumericArgument:
  Enabled: true
Performance/RedundantSortBlock:
  Enabled: true
Performance/RedundantStringChars:
  Enabled: true
Performance/ReverseFirst:
  Enabled: true
Performance/SortReverse:
  Enabled: true
Performance/Squeeze:
  Enabled: true
Performance/StringInclude:
  Enabled: true
Performance/Sum:
  Enabled: true
Performance/ArraySemiInfiniteRangeSlice:
  Enabled: true
Performance/BlockGivenWithExplicitBlock:
  Enabled: true
Performance/CollectionLiteralInLoop:
  Enabled: true
Performance/ConstantRegexp:
  Enabled: true
Performance/MethodObjectAsBlock:
  Enabled: false
Performance/RedundantEqualityComparisonBlock:
  Enabled: true
Performance/RedundantSplitRegexpArgument:
  Enabled: true
Performance/MapCompact:
  Enabled: true
Performance/ConcurrentMonotonicTime: # new in 1.12
  Enabled: true
Performance/StringIdentifierArgument: # new in 1.13
  Enabled: true

# ============================== Metrics ============================
Metrics/ClassLength:
  Max: 180
Metrics/BlockLength:
  Enabled: true
Metrics/MethodLength:
  Max: 25
Metrics/AbcSize:
  Max: 40

# ============================== Naming =============================
Naming/PredicateName:
  ForbiddenPrefixes:
    - is_
Naming/FileName:
  Enabled: true
  Exclude:
    - 'Gemfile'
Naming/MethodParameterName:
  Enabled: false
Naming/AccessorMethodName:
  Enabled: false
Naming/InclusiveLanguage:
  Enabled: true
Naming/BlockForwarding: # new in 1.24
  Enabled: true

# ============================== Layout =============================
Layout/LineLength:
  Max: 140
Layout/HashAlignment:
  EnforcedHashRocketStyle: key
  EnforcedColonStyle: key
Layout/ParameterAlignment:
  EnforcedStyle: with_fixed_indentation
Layout/CaseIndentation:
  EnforcedStyle: case
  IndentOneStep: false
Layout/MultilineMethodCallIndentation:
  Enabled: true
  EnforcedStyle: indented
Layout/SpaceBeforeBlockBraces:
  EnforcedStyle: space
  EnforcedStyleForEmptyBraces: space
Layout/EmptyLines:
  Enabled: true
Layout/EmptyLineAfterMagicComment:
  Enabled: false
Layout/EmptyLinesAroundBlockBody:
  Enabled: true
Layout/EndAlignment:
  EnforcedStyleAlignWith: variable
Layout/FirstHashElementIndentation:
  EnforcedStyle: consistent
Layout/HeredocIndentation:
  Enabled: false
Layout/RescueEnsureAlignment:
  Enabled: false
Layout/EmptyLinesAroundAttributeAccessor:
  Enabled: true
Layout/SpaceAroundMethodCallOperator:
  Enabled: true
Layout/SpaceBeforeBrackets:
  Enabled: true
Layout/LineEndStringConcatenationIndentation:
  Enabled: true
Layout/LineContinuationLeadingSpace: # new in 1.31
  Enabled: true
Layout/LineContinuationSpacing: # new in 1.31
  Enabled: true

# ============================== Style ==============================
Style/RescueModifier:
  Enabled: true
Style/PercentLiteralDelimiters:
  PreferredDelimiters:
    default: '[]'
    '%i':    '[]'
    '%w':    '[]'
  Exclude:
    - 'config/routes.rb'
Style/StringLiterals:
  Enabled: true
Style/AsciiComments:
  Enabled: false
Style/Copyright:
  Enabled: false
Style/SafeNavigation:
  Enabled: false
Style/Lambda:
  Enabled: false
Style/Alias:
  Enabled: true
  EnforcedStyle: prefer_alias_method
Style/ClassAndModuleChildren:
  Enabled: true
  EnforcedStyle: nested
Style/TrailingCommaInArrayLiteral:
  Enabled: true
  EnforcedStyleForMultiline: no_comma
Style/RescueStandardError:
  Enabled: true
  EnforcedStyle: explicit
Style/InverseMethods:
  AutoCorrect: false
  Enabled: true
Style/IfUnlessModifier:
  Enabled: false
Style/SpecialGlobalVars:
  Enabled: false
Style/BlockComments:
  Enabled: false
Style/GuardClause:
  Enabled: false
Style/TrailingCommaInHashLiteral:
  Enabled: false
Style/ExponentialNotation:
  Enabled: true
Style/HashEachMethods:
  Enabled: true
Style/HashTransformKeys:
  Enabled: true
Style/HashTransformValues:
  Enabled: true
Style/RedundantFetchBlock:
  Enabled: true
Style/RedundantRegexpCharacterClass:
  Enabled: true
Style/RedundantRegexpEscape:
  Enabled: true
Style/SlicingWithRange:
  Enabled: true
Style/AccessorGrouping:
  Enabled: false
Style/ArrayCoercion:
  Enabled: true
Style/BisectedAttrAccessor:
  Enabled: true
Style/CaseLikeIf:
  Enabled: true
Style/HashAsLastArrayItem:
  Enabled: true
Style/HashLikeCase:
  Enabled: true
Style/RedundantAssignment:
  Enabled: true
Style/RedundantFileExtensionInRequire:
  Enabled: true
Style/ExplicitBlockArgument:
  Enabled: true
Style/GlobalStdStream:
  Enabled: true
Style/OptionalBooleanParameter:
  Enabled: true
Style/SingleArgumentDig:
  Enabled: true
Style/StringConcatenation:
  Enabled: true
Style/ClassEqualityComparison:
  Enabled: true
Style/CombinableLoops:
  Enabled: true
Style/KeywordParametersOrder:
  Enabled: false
Style/RedundantSelfAssignment:
  Enabled: true
Style/SoleNestedConditional:
  Enabled: true
Style/ArgumentsForwarding:
  Enabled: true
Style/CollectionCompact:
  Enabled: true
Style/DocumentDynamicEvalDefinition:
  Enabled: false
Style/NegatedIfElseCondition:
  Enabled: true
Style/NilLambda:
  Enabled: true
Style/SwapValues:
  Enabled: true
Style/RedundantArgument:
  Enabled: true
Style/HashExcept:
  Enabled: true
Style/EndlessMethod:
  Enabled: true
Style/IfWithBooleanLiteralBranches:
  Enabled: true
Style/HashConversion:
  Enabled: true
Style/Documentation:
  Enabled: false
Style/InPatternThen:
  Enabled: true
Style/MultilineInPatternThen:
  Enabled: true
Style/QuotedSymbols:
  Enabled: true
Style/StringChars:
  Enabled: true
Style/EmptyHeredoc: # new in 1.32
  Enabled: true
Style/EnvHome: # new in 1.29
  Enabled: true
Style/FetchEnvVar: # new in 1.28
  Enabled: true
Style/FileRead: # new in 1.24
  Enabled: true
Style/FileWrite: # new in 1.24
  Enabled: true
Style/MagicCommentFormat: # new in 1.35
  Enabled: true
Style/MapCompactWithConditionalBlock: # new in 1.30
  Enabled: true
Style/MapToHash: # new in 1.24
  Enabled: true
Style/NestedFileDirname: # new in 1.26
  Enabled: true
Style/NumberedParameters: # new in 1.22
  Enabled: true
Style/NumberedParametersLimit: # new in 1.22
  Enabled: true
Style/ObjectThen: # new in 1.28
  Enabled: true
Style/OpenStructUse: # new in 1.23
  Enabled: true
Style/OperatorMethodCall: # new in 1.37
  Enabled: true
Style/RedundantEach: # new in 1.38
  Enabled: true
Style/RedundantInitialize: # new in 1.27
  Enabled: true
Style/RedundantSelfAssignmentBranch: # new in 1.19
  Enabled: true
Style/RedundantStringEscape: # new in 1.37
  Enabled: true
Style/SelectByRegexp: # new in 1.22
  Enabled: true

# ============================== Security ==============================
Security/CompoundHash: # new in 1.28
  Enabled: true
Security/IoMethods: # new in 1.22
  Enabled: true

# ============================== Lint ==============================
Lint/DuplicateMethods:
  Enabled: false
Lint/AmbiguousOperator:
  Enabled: false
Lint/DeprecatedOpenSSLConstant:
  Enabled: true
Lint/MixedRegexpCaptureTypes:
  Enabled: true
Lint/RaiseException:
  Enabled: true
Lint/StructNewOverride:
  Enabled: true
Lint/DuplicateElsifCondition:
  Enabled: true
Lint/BinaryOperatorWithIdenticalOperands:
  Enabled: true
Lint/DuplicateRescueException:
  Enabled: true
Lint/EmptyConditionalBody:
  Enabled: true
Lint/FloatComparison:
  Enabled: true
Lint/MissingSuper:
  Enabled: false
Lint/OutOfRangeRegexpRef:
  Enabled: true
Lint/SelfAssignment:
  Enabled: true
Lint/TopLevelReturnWithArgument:
  Enabled: true
Lint/UnreachableLoop:
  Enabled: true
Layout/BeginEndAlignment:
  Enabled: true
Lint/ConstantDefinitionInBlock:
  Enabled: true
Lint/DuplicateRequire:
  Enabled: true
Lint/EmptyFile:
  Enabled: true
Lint/HashCompareByIdentity:
  Enabled: true
Lint/IdentityComparison:
  Enabled: true
Lint/RedundantSafeNavigation:
  Enabled: true
Lint/TrailingCommaInAttributeDeclaration:
  Enabled: true
Lint/UselessMethodDefinition:
  Enabled: true
Lint/UselessTimes:
  Enabled: true
Lint/DuplicateBranch:
  Enabled: true
Lint/DuplicateRegexpCharacterClassElement:
  Enabled: true
Lint/EmptyBlock:
  Enabled: true
Lint/EmptyClass:
  Enabled: true
Lint/NoReturnInBeginEndBlocks:
  Enabled: true
Lint/ToEnumArguments:
  Enabled: true
Lint/UnmodifiedReduceAccumulator:
  Enabled: true
Lint/UnexpectedBlockArity:
  Enabled: true
Lint/DeprecatedConstants:
  Enabled: true
Lint/LambdaWithoutLiteralBlock:
  Enabled: true
Lint/NumberedParameterAssignment:
  Enabled: true
Lint/OrAssignmentToConstant:
  Enabled: true
Lint/RedundantDirGlobSort:
  Enabled: true
Lint/SymbolConversion:
  Enabled: true
Lint/TripleQuotes:
  Enabled: true
Lint/AmbiguousAssignment:
  Enabled: true
Lint/EmptyInPattern:
  Enabled: true
Lint/AmbiguousOperatorPrecedence: # new in 1.21
  Enabled: true
Lint/AmbiguousRange: # new in 1.19
  Enabled: true
Lint/ConstantOverwrittenInRescue: # new in 1.31
  Enabled: true
Lint/DuplicateMagicComment: # new in 1.37
  Enabled: true
Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21
  Enabled: true
Lint/NonAtomicFileOperation: # new in 1.31
  Enabled: true
Lint/RefinementImportMethods: # new in 1.27
  Enabled: true
Lint/RequireRangeParentheses: # new in 1.32
  Enabled: true
Lint/RequireRelativeSelfPath: # new in 1.22
  Enabled: true
Lint/UselessRuby2Keywords: # new in 1.23
  Enabled: true


================================================
FILE: CHANGELOG.md
================================================
# 2.1.1
* Fix logging with symbolized keys JSON
* Unknown formats return raw `Response::ResultSelt` like regular JSON query
* Added methods `statistics`, `summary`, `headers` and `types` to `Response::ResultSet`

# 2.1.0
* `ClickHouse.connection.insert` now returns `ClickHouse::Response::Summary` object
  with methods `headers`, `summary`, `written_rows`, `written_bytes`, etc... 
* `ClickHouse.connection.insert(columns: ["id"], values: [1])` now uses `JSONCompactEachRow` by default
  (to increase JSON serialization speed)
* Methods `insert_rows` and `insert_compact` added to `connection`
* Added ability to pass object directly to insert like:
  `ClickHouse.connection.insert("table", {id: 1})` or
  `ClickHouse.connection.insert("table", [{id: 1})]` (for ruby < 3.0 use `ClickHouse.connection.insert("table", [{id: 1}], {})`)
* 🔥 Added config option `json_serializer` (one of `ClickHouse::Serializer::JsonSerializer`, `ClickHouse::Serializer::JsonOjSerializer`)
* 🔥 Added config option `symbolize_keys`
* 🔥 Added type serialization for INSERT statements, example below:

```sql
CREATE TABLE assets(visible Boolean, tags Array(Nullable(String))) ENGINE Memory
```

```ruby
# cache table schema in a class variable
@schema = ClickHouse.connection.table_schema('assets')

# Json each row
ClickHouse.connection.insert('assets', @schema.serialize({'visible' => true, 'tags' => ['ruby']}))

# Json compact
ClickHouse.connection.insert('assets', columns: %w[visible tags]) do |buffer|
  buffer << [
    @schema.serialize_column("visible", true),
    @schema.serialize_column("tags", ['ruby']),
  ]
end
```

# 2.0.0
* Fixed `Bigdecimal` casting with high precision
* Added nested `type casting like Array(Array(Array(Nullable(T))))`
* Added `Map(T1, T2)` support
* Added `Tuple(T1, T2)` support
* Added support for `Faraday` v1 and v2
* Added support for `Oj` parser
* Time types return `Time` class instead of `DateTime` for now

# 1.6.3
* [PR](https://github.com/shlima/click_house/pull/38) Add option format for insert
* [PR](https://github.com/shlima/click_house/pull/34) Support X-ClickHouse-Exception-Code header
* [ISSUE](https://github.com/shlima/click_house/issues/33) Fix parameterized types parsing
* Added LowCardinality DDL support
* Fixed body logging with POST queries

# 1.6.2
* [PR](https://github.com/shlima/click_house/pull/31) Add rows_before_limit_at_least to ResultSet
* [PR](https://github.com/shlima/click_house/pull/29) Force JSON format by using "default_format" instead of modifying the query

# 1.6.1
* [PR](https://github.com/shlima/click_house/pull/26) call logging middleware when an error is raised

# 1.6.0
* [PR](https://github.com/shlima/click_house/pull/19) handle value returned as nil in float and integer types (case of Aggregate Function Combinators) 
* [PR](https://github.com/shlima/click_house/pull/18) Fix Faraday deprecation

# 1.5.0
* add support for 'WITH TOTALS' modifier in response
* send SQL in GET request's body [#12](https://github.com/shlima/click_house/pull/12)
* add support of 'WITH TOTALS' on a resulting set

# 1.4.0
* fix decimal type casting [#11](https://github.com/shlima/click_house/issues/11)

# 1.3.9
* add `ClickHouse.connection.add_index`, `ClickHouse.connection.drop_index`

# 1.3.8
* fix `DateTime` casting for queries like `ClickHouse.connection.select_value('select NOW()')` 
* fix resulting set console inspection

# 1.3.7
* specify required ruby version [#10](https://github.com/shlima/click_house/issues/10)

# 1.3.6
* fix ruby 2.7 warning `maybe ** should be added to the call` on `ClickHouse.connection.databases`

# 1.3.5
* added `ClickHouse.connection.explain("sql")` 

# 1.3.4
* added `ClickHouse.type_names(nullable: false)`
* fixed `connection#create_table` column definitions
* `ClickHouse.add_type` now handles Nullable types automatically

# 1.3.3
* fix logger typo

# 1.3.2
* fix null logger for windows users

# 1.3.1
* added request [headers](https://github.com/shlima/click_house/pull/8) support

# 1.3.0
* added support for IPv4/IPv6 types

# 1.2.7
* rubocop version bump

# 1.2.6
* Datetime64 field type support [#3](https://github.com/shlima/click_house/pull/3)


================================================
FILE: Gemfile
================================================
# frozen_string_literal: true

source 'https://rubygems.org'

git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

# Specify your gem's dependencies in click_house.gemspec
gemspec


================================================
FILE: Gemfile_faraday1
================================================
# frozen_string_literal: true

source 'https://rubygems.org'

git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

# lock faraday to v1
gem 'faraday', '< 2'

# Specify your gem's dependencies in click_house.gemspec
gemspec


================================================
FILE: LICENCE.txt
================================================
MIT License

COPYRIGHT (C) 2019 ALIAKSANDR SHYLAU

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
.PHONY: help

.BUNDLE_GEMFILE:=
.REQUIRE:=./spec/spec_helper

help:
	@echo 'Available targets:'
	@echo '  make dockerize OR make ARGS="--build" dockerize'
	@echo '  make release'
	@echo '  '
	@echo '  make faraday1 bundle'
	@echo '  make faraday2 bundle'
	@echo '  '
	@echo '  make faraday1 rspec'
	@echo '  make faraday2 rspec'
	@echo '  make faraday2 oj rspec'

dockerize:
	docker-compose up ${ARGS}

release:
	bin/release.sh

faraday1:
	$(eval .BUNDLE_GEMFILE=Gemfile_faraday1)

faraday2:
	$(eval .BUNDLE_GEMFILE=Gemfile_faraday2)

oj:
	$(eval .REQUIRE=./spec/oj_helper)

bundle:
	BUNDLE_GEMFILE=${.BUNDLE_GEMFILE} bundle

rspec:
	BUNDLE_GEMFILE=${.BUNDLE_GEMFILE} rspec --require ${.REQUIRE} spec

================================================
FILE: README.md
================================================
![](./doc/logo.svg?sanitize=true)

# ClickHouse Ruby driver

![CI](https://github.com/shlima/click_house/workflows/CI/badge.svg)
[![Code Climate](https://codeclimate.com/github/shlima/click_house/badges/gpa.svg)](https://codeclimate.com/github/shlima/click_house)
[![Gem Version](https://badge.fury.io/rb/click_house.svg)](https://badge.fury.io/rb/click_house)

```bash
gem install click_house
```

A modern Ruby database driver for ClickHouse. [ClickHouse](https://clickhouse.yandex)
is a high-performance column-oriented database management system developed by
[Yandex](https://yandex.com/company) which operates Russia's most popular search engine.

> This development was inspired by currently [unmaintainable alternative](https://github.com/archan937/clickhouse)
> but rewritten and well tested

### Why use the HTTP interface and not the TCP interface?

Well, the developers of ClickHouse themselves [discourage](https://github.com/yandex/ClickHouse/issues/45#issuecomment-231194134) using the TCP interface.

> TCP transport is more specific, we don't want to expose details.
Despite 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.

Yandex uses HTTP interface for working from Java and Perl, Python and Go as well as shell scripts.

# TOC

* [Configuration](#configuration)
* [Usage](#usage)
* [Queries](#queries)
* [Insert](#insert)
* [Create a table](#create-a-table)
* [Alter table](#alter-table)
* [Type casting](#type-casting)
* [Using with a connection pool](#using-with-a-connection-pool)
* [Using with Rails](#using-with-rails)
* [Using with ActiveRecord](#using-with-activerecord)
* [Using with RSpec](#using-with-rspec)
* [Development](#development)

## Configuration

```ruby
ClickHouse.config do |config|
  config.logger = Logger.new(STDOUT)
  config.adapter = :net_http
  config.database = 'metrics'
  config.url = 'http://localhost:8123'
  config.timeout = 60
  config.open_timeout = 3
  config.ssl_verify = false
  # set to true to symbolize keys for SELECT and INSERT statements (type casting)
  config.symbolize_keys = false
  config.headers = {}

  # or provide connection options separately
  config.scheme = 'http'
  config.host = 'localhost'
  config.port = 'port'

  # if you use HTTP basic Auth
  config.username = 'user'
  config.password = 'password'

  # if you want to add settings to all queries
  config.global_params = { mutations_sync: 1 }
  
  # choose a ruby JSON parser (default one)
  config.json_parser = ClickHouse::Middleware::ParseJson
  # or Oj parser
  config.json_parser = ClickHouse::Middleware::ParseJsonOj

  # JSON.dump (default one)
  config.json_serializer = ClickHouse::Serializer::JsonSerializer
  # or Oj.dump
  config.json_serializer = ClickHouse::Serializer::JsonOjSerializer
end
```

Alternative, you can assign configuration parameters via a hash

```ruby
ClickHouse.config.assign(logger: Logger.new(STDOUT))
```

Now you are able to communicate with ClickHouse:

```ruby
ClickHouse.connection.ping #=> true
```
You can easily build a new raw connection and override any configuration parameter
(such as database name, connection address)

```ruby
@connection = ClickHouse::Connection.new(ClickHouse::Config.new(logger: Rails.logger))
@connection.ping
```

## Usage

```ruby
ClickHouse.connection.ping #=> true
ClickHouse.connection.replicas_status #=> true

ClickHouse.connection.databases #=> ["default", "system"]
ClickHouse.connection.create_database('metrics', if_not_exists: true, engine: nil, cluster: nil)
ClickHouse.connection.drop_database('metrics', if_exists: true, cluster: nil)

ClickHouse.connection.tables #=> ["visits"]
ClickHouse.connection.describe_table('visits') #=> [{"name"=>"id", "type"=>"FixedString(16)", "default_type"=>""}]
ClickHouse.connection.table_exists?('visits', temporary: nil) #=> true
ClickHouse.connection.drop_table('visits', if_exists: true, temporary: nil, cluster: nil)
ClickHouse.connection.create_table(*) # see <Create a table> section
ClickHouse.connection.truncate_table('name', if_exists: true, cluster: nil)
ClickHouse.connection.truncate_tables(['table_1', 'table_2'], if_exists: true, cluster: nil)
ClickHouse.connection.truncate_tables # will truncate all tables in database
ClickHouse.connection.rename_table('old_name', 'new_name', cluster: nil)
ClickHouse.connection.rename_table(%w[table_1 table_2], %w[new_1 new_2], cluster: nil)
ClickHouse.connection.alter_table('table', 'DROP COLUMN user_id', cluster: nil)
ClickHouse.connection.add_index('table', 'ix', 'has(b, a)', type: 'minmax', granularity: 2, cluster: nil)
ClickHouse.connection.drop_index('table', 'ix', cluster: nil)

ClickHouse.connection.select_all('SELECT * FROM visits')
ClickHouse.connection.select_one('SELECT * FROM visits LIMIT 1')
ClickHouse.connection.select_value('SELECT ip FROM visits LIMIT 1')
ClickHouse.connection.explain('SELECT * FROM visits CROSS JOIN visits')
```

## Queries
### Select All

Select all type-casted result set

```ruby
@result = ClickHouse.connection.select_all('SELECT * FROM visits')

# all enumerable methods are delegated like #each, #map, #select etc
# results of #to_a is TYPE CASTED
@result.to_a #=> [{"date"=>#<Date: 2000-01-01>, "id"=>1}]

# raw results (WITHOUT type casting)
# much faster if selecting a large amount of data
@result.data #=> [{"date"=>"2000-01-01", "id"=>1}, {"date"=>"2000-01-02", "id"=>2}]

# you can access raw data
@result.meta #=> [{"name"=>"date", "type"=>"Date"}, {"name"=>"id", "type"=>"UInt32"}]
@result.statistics #=> {"elapsed"=>0.0002271, "rows_read"=>2, "bytes_read"=>12}
@result.summary #=> ClickHouse::Response::Summary
@result.headers #=> {"x-clickhouse-query-id"=>"9bf5f604-31fc-4eff-a4b5-277f2c71d199"}
@result.types #=> [Hash<String|Symbol, ClickHouse::Ast::Statement>]
```

### Select Value

Select value returns exactly one type-casted value

```ruby
ClickHouse.connection.select_value('SELECT COUNT(*) from visits') #=> 0
ClickHouse.connection.select_value("SELECT toDate('2019-01-01')") #=> #<Date: 2019-01-01>
ClickHouse.connection.select_value("SELECT toDateOrZero(NULL)") #=> nil
```

### Select One

Returns a record hash with the column names as keys and column values as values.

```ruby
ClickHouse.connection.select_one('SELECT date, SUM(id) AS sum FROM visits GROUP BY date')
#=> {"date"=>#<Date: 2000-01-01>, "sum"=>1}
```

### Execute Raw SQL

By default, gem provides parser for `JSON` and `CSV` response formats. Type conversion
available for the `JSON`.

```ruby
# format not specified
response = ClickHouse.connection.execute <<~SQL
  SELECT count(*) AS counter FROM rspec
SQL

response.body #=> "2\n"

# JSON
response = ClickHouse.connection.execute <<~SQL
  SELECT count(*) AS counter FROM rspec FORMAT JSON
SQL

response.body #=> {"meta"=>[{"name"=>"counter", "type"=>"UInt64"}], "data"=>[{"counter"=>"2"}], "rows"=>1, "statistics"=>{"elapsed"=>0.0002412, "rows_read"=>2, "bytes_read"=>4}}

# CSV
response = ClickHouse.connection.execute <<~SQL
  SELECT count(*) AS counter FROM rspec FORMAT CSV
SQL

response.body #=> [["2"]]

# You may use any format supported by ClickHouse
response = ClickHouse.connection.execute <<~SQL
  SELECT count(*) AS counter FROM rspec FORMAT RowBinary
SQL

response.body #=> "\u0002\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
```

## Insert

When column names and values are transferred separately, data sends to the server 
using `JSONCompactEachRow` format by default.

```ruby
ClickHouse.connection.insert('table', columns: %i[id name]) do |buffer|
  buffer << [1, 'Mercury']
  buffer << [2, 'Venus']
end

# or
ClickHouse.connection.insert('table', columns: %i[id name], values: [[1, 'Mercury'], [2, 'Venus']])
```

When rows are passed as an Array or a Hash, data sends to the server
using `JSONEachRow` format by default.

```ruby
ClickHouse.connection.insert('table', [{ name: 'Sun', id: 1 }, { name: 'Moon', id: 2 }])

# or
ClickHouse.connection.insert('table', { name: 'Sun', id: 1 })

# for ruby < 3.0 provide an extra argument
ClickHouse.connection.insert('table', { name: 'Sun', id: 1 }, {})

# or
ClickHouse.connection.insert('table') do |buffer|
  buffer << { name: 'Sun', id: 1 }
  buffer << { name: 'Moon', id: 2 }
end
```

Sometimes it's needed to use other format than `JSONEachRow` For example if you want to send BigDecimal's 
you could use `JSONStringsEachRow` format so string representation of `BigDecimal` will be parsed:

```ruby
ClickHouse.connection.insert('table', { name: 'Sun', id: '1' }, format: 'JSONStringsEachRow')
# or
ClickHouse.connection.insert_rows('table', { name: 'Sun', id: '1' }, format: 'JSONStringsEachRow')
# or
ClickHouse.connection.insert_compact('table', columns: %w[name id], values: %w[Sun 1], format: 'JSONCompactStringsEachRow')
```

See the [type casting](#type-casting) section to insert the data in a proper way.

## Create a table
### Create table using DSL

```ruby
ClickHouse.connection.create_table('visits', if_not_exists: true, engine: 'MergeTree(date, (year, date), 8192)') do |t|
  t.FixedString :id, 16
  t.UInt16      :year, low_cardinality: true
  t.Date        :date
  t.DateTime    :time, 'UTC'
  t.Decimal     :money, 5, 4
  t.String      :event
  t.UInt32      :user_id
  t.IPv4        :ipv4
  t.IPv6        :ipv6
end
```

### Create nullable columns

```ruby
ClickHouse.connection.create_table('visits', engine: 'TinyLog') do |t|
  t.UInt16 :id, 16, nullable: true
end
```

### Set column options

```ruby
ClickHouse.connection.create_table('visits', engine: 'MergeTree(date, (year, date), 8192)') do |t|
  t.UInt16  :year
  t.Date    :date
  t.UInt16  :id, 16, default: 0, ttl: 'date + INTERVAL 1 DAY'
end
```

### Define column with custom SQL

```ruby
ClickHouse.connection.create_table('visits', engine: 'TinyLog') do |t|
  t << "vendor Enum('microsoft' = 1, 'apple' = 2)"
  t << "tags Array(String)"
end
```

### Define nested structures

```ruby
ClickHouse.connection.create_table('visits', engine: 'TinyLog') do |t|
  t.UInt8 :id
  t.Nested :json do |n|
    n.UInt8 :cid
    n.Date  :created_at
    n.Date  :updated_at
  end
end
```

### Set table options

```ruby
ClickHouse.connection.create_table('visits',
  order: 'year',
  ttl: 'date + INTERVAL 1 DAY',
  sample: 'year',
  settings: 'index_granularity=8192',
  primary_key: 'year',
  engine: 'MergeTree') do |t|
  t.UInt16  :year
  t.Date    :date
end
```

### Create table with raw SQL

```ruby
ClickHouse.connection.execute <<~SQL
  CREATE TABLE visits(int Nullable(Int8), date Nullable(Date)) ENGINE TinyLog
SQL
```

## Alter table
### Alter table with DSL
```ruby
ClickHouse.connection.add_column('table', 'column_name', :UInt64, default: nil, if_not_exists: nil, after: nil, cluster: nil)
ClickHouse.connection.drop_column('table', 'column_name', if_exists: nil, cluster: nil)
ClickHouse.connection.clear_column('table', 'column_name', partition: 'partition_name', if_exists: nil, cluster: nil)
ClickHouse.connection.modify_column('table', 'column_name', type: :UInt64, default: nil, if_exists: false, cluster: nil)
```

### Alter table with SQL

```ruby
# By SQL in argument
ClickHouse.connection.alter_table('table', 'DROP COLUMN user_id', cluster: nil)

# By SQL in a block
ClickHouse.connection.alter_table('table', cluster: nil) do
  <<~SQL
    MOVE PART '20190301_14343_16206_438' TO VOLUME 'slow'
  SQL
end
```

## Type casting

By default gem provides all necessary type casting, but you may overwrite or define
your own logic. if you need to redefine all built-in types with your implementation,
just clear the default type system:

```ruby
ClickHouse.types.clear
ClickHouse.types # => {}
ClickHouse.types.default #=> #<ClickHouse::Type::UndefinedType>
```

Type casting works automatically when fetching data, when inserting data, you must serialize the types yourself

```sql
CREATE TABLE assets(visible Boolean, tags Array(Nullable(String))) ENGINE Memory
```

```ruby
# cache table schema in a class variable
@schema = ClickHouse.connection.table_schema('assets')

# Json each row
ClickHouse.connection.insert('assets', @schema.serialize({'visible' => true, 'tags' => ['ruby']}))

# Json compact
ClickHouse.connection.insert('assets', columns: %w[visible tags]) do |buffer|
  buffer << [
    @schema.serialize_column("visible", true),
    @schema.serialize_column("tags", ['ruby']),
  ]
end
```

## Using with a connection pool

```ruby
require 'connection_pool'

ClickHouse.connection = ConnectionPool.new(size: 2) do
  ClickHouse::Connection.new(ClickHouse::Config.new(url: 'http://replica.example.com'))
end

ClickHouse.connection.with do |conn|
  conn.tables
end
```

## Using with Rails

```yml
# config/click_house.yml

default: &default
  url: http://localhost:8123
  timeout: 60
  open_timeout: 3

development:
  database: ecliptic_development
  <<: *default

test:
  database: ecliptic_test
  <<: *default

production:
  <<: *default
  database: ecliptic_production
```

```ruby
# config/initializers/click_house.rb

ClickHouse.config do |config|
  config.logger = Rails.logger
  config.assign(Rails.application.config_for('click_house'))
end
```

```ruby
# lib/tasks/click_house.rake
namespace :click_house do
  task prepare: :environment do
    @environments = Rails.env.development? ? %w[development test] : [Rails.env]
  end

  task drop: :prepare do
    @environments.each do |env|
      config = ClickHouse.config.clone.assign(Rails.application.config_for('click_house', env: env))
      connection = ClickHouse::Connection.new(config)
      connection.drop_database(config.database, if_exists: true)
    end
  end

  task create: :prepare do
    @environments.each do |env|
      config = ClickHouse.config.clone.assign(Rails.application.config_for('click_house', env: env))
      connection = ClickHouse::Connection.new(config)
      connection.create_database(config.database, if_not_exists: true)
    end
  end
end
```

Prepare the ClickHouse database:

```bash
rake click_house:drop click_house:create
```

If your are using SQL Database in Rails, you can manage ClickHouse migrations
using `ActiveRecord::Migration` mechanism

```ruby
class CreateAdvertVisits < ActiveRecord::Migration[6.0]
  def up
    ClickHouse.connection.create_table('visits', engine: 'MergeTree(date, (account_id, advert_id), 512)') do |t|
      t.UInt16   :account_id
      t.UInt16   :user_id
      t.Date     :date
    end
  end

  def down
    ClickHouse.connection.drop_table('visits')
  end
end
```

## Using with ActiveRecord

if you use `ActiveRecord`, you can use the ORM query builder by using fake models
(empty tables must be present in the SQL database `create_table :visits`)

```ruby
class ClickHouseRecord < ActiveRecord::Base
  self.abstract_class = true

  class << self
    def agent
      ClickHouse.connection
    end

    def insert(*argv, &block)
      agent.insert(table_name, *argv, &block)
    end

    def select_one
      agent.select_one(current_scope.to_sql)
    end

    def select_value
      agent.select_value(current_scope.to_sql)
    end

    def select_all
      agent.select_all(current_scope.to_sql)
    end

    def explain
      agent.explain(current_scope.to_sql)
    end
  end
end
````

````ruby
# FAKE MODEL FOR ClickHouse
class Visit < ClickHouseRecord
  scope :with_os, -> { where.not(os_family_id: nil) }
end

Visit.with_os.select('COUNT(*) as counter').group(:ipv4).select_all
#=> [{ 'ipv4' => 1455869, 'counter' => 104 }]

Visit.with_os.select('COUNT(*)').select_value
#=> 20_345_678

Visit.where(user_id: 1).select_one
#=> { 'ipv4' => 1455869, 'user_id' => 1 }
````

## Using with RSpec

You can clear the data table before each test with RSpec

```ruby
RSpec.configure do |config|
  config.before(:each, truncate_click_house: true) do
    ClickHouse.connection.truncate_tables
  end
end
```

```ruby
RSpec.describe Api::MetricsCountroller, truncate_click_house: true do
  it { }
  it { }
end
```

## Development

```bash
make dockerize
rspec
rubocop
```


================================================
FILE: Rakefile
================================================
# frozen_string_literal: true

require 'bundler/gem_tasks'
require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new(:spec)

task default: :spec


================================================
FILE: bin/console
================================================
#!/usr/bin/env ruby

require 'bundler/setup'
require 'click_house'
require 'pry'

ClickHouse.config do |config|
  config.logger = Logger.new(STDOUT)
end

Pry.start


================================================
FILE: bin/release.sh
================================================
#!/usr/bin/env bash

rm ./*.gem
gem build click_house.gemspec
gem push click_house-*


================================================
FILE: bin/setup
================================================
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx

bundle install

# Do any other automated setup that you need to do here


================================================
FILE: click_house.gemspec
================================================
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'click_house/version'

Gem::Specification.new do |spec|
  spec.name          = 'click_house'
  spec.version       = ClickHouse::VERSION
  spec.authors       = ['Aliaksandr Shylau']
  spec.email         = ['alex.shilov.by@gmail.com']
  spec.summary       = 'Modern Ruby database driver for ClickHouse'
  spec.description   = 'Yandex ClickHouse database interface for Ruby'
  spec.homepage      = 'https://github.com/shlima/click_house'
  spec.required_ruby_version = '>= 2.7.0'

  # Specify which files should be added to the gem when it is released.
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
  spec.files         = Dir.chdir(File.expand_path('..', __FILE__)) do
    `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
  end
  spec.require_paths = ['lib']

  spec.add_dependency 'faraday', '>= 1.7', '< 3'
  spec.add_dependency 'activesupport'
  spec.add_development_dependency 'bundler'
  spec.add_development_dependency 'rake'
  spec.add_development_dependency 'oj'
  spec.add_development_dependency 'rspec'
  spec.add_development_dependency 'pry'
  spec.add_development_dependency 'rubocop'
  spec.add_development_dependency 'rubocop-performance'
end


================================================
FILE: docker-compose.yml
================================================
version: '3.5'

services:
  clickhouse:
    image: clickhouse/clickhouse-server:22.9
    ports:
      - "8123:8123"
      - "9000:9000"
      - "9009:9009"
    ulimits:
      nproc: 65535
      nofile:
        soft: 262144
        hard: 262144
    volumes:
      - ./tmp/clickhouse-data:/opt/clickhouse/data
    networks:
      - default

networks:
  default:


================================================
FILE: lib/click_house/ast/parser.rb
================================================
# frozen_string_literal: true

require 'stringio'

module ClickHouse
  module Ast
    class Parser
      OPEN = '('
      CLOSED = ')'
      COMMA = ','
      SPACE = ' '

      attr_reader :input

      # @param input [String]
      def initialize(input)
        @input = input
      end

      # @refs https://clickhouse.com/docs/en/sql-reference/data-types/
      # Map(String, Decimal(10, 5))
      # Array(Array(Array(Array(Nullable(Int, String)))))
      def parse
        ticker = Ticker.new
        control = false

        input.each_char do |char|
          # cases like (1,<space after comma> 3)
          next if control && char == SPACE

          case char
          when OPEN
            control = true
            ticker.open
          when CLOSED
            control = true
            ticker.close
          when COMMA
            control = true
            ticker.comma
          else
            control = false
            ticker.char(char)
          end
        end

        # if a single type like "Int"
        ticker.current.name! unless control
        ticker.current
      end
    end
  end
end


================================================
FILE: lib/click_house/ast/statement.rb
================================================
# frozen_string_literal: true

require 'stringio'

module ClickHouse
  module Ast
    class Statement
      PLACEHOLDER_S = '%s'
      PLACEHOLDER_D = '%d'
      DIGIT_RE = /\A\d+\Z/.freeze

      attr_reader :name
      attr_accessor :caster

      def initialize(name: '')
        @buffer = ''
        @name = name
      end

      # @param value [String]
      def print(value)
        @buffer = "#{@buffer}#{value}"
      end

      def name!
        @name = @buffer
        @buffer = ''
      end

      def argument!
        add_argument(Statement.new(name: @buffer))
        @buffer = ''
      end

      # @param st [Statement]
      def add_argument(st)
        arguments.push(st)
      end

      # @param other [Statement]
      def merge(other)
        if other.named?
          add_argument(other)
        else
          @arguments = arguments.concat(other.arguments)
        end
      end

      def named?
        !@name.empty?
      end

      def buffer?
        !@buffer.empty?
      end

      # @return [Array<Statement>]
      def arguments
        @arguments ||= []
      end

      # @return [Array]
      # cached argument values to increase the casting perfomance
      def argument_values
        @argument_values ||= arguments.map(&:value)
      end

      def argument_first!
        # TODO: raise an error if multiple arguments
        @argument_first ||= arguments.first
      end

      def placeholder
        return @placeholder if defined?(@placeholder)

        @placeholder = digit? ? PLACEHOLDER_D : PLACEHOLDER_S
      end

      def digit?
        name.match?(DIGIT_RE)
      end

      def value
        @value ||=
          case placeholder
          when PLACEHOLDER_D
            Integer(name)
          when PLACEHOLDER_S
            # remove leading and trailing quotes
            name[1..-2]
          else
            raise "unknown value extractor for <#{placeholder}>"
          end
      end

      def to_s
        out = StringIO.new
        out.print(name.empty? ? 'NO_NAME' : name)
        out.print("<#{@buffer}>") unless @buffer.empty?

        if arguments.any?
          out.print("(#{arguments.join(',')})")
        end

        out.string
      end
    end
  end
end


================================================
FILE: lib/click_house/ast/ticker.rb
================================================
# frozen_string_literal: true

require 'stringio'

module ClickHouse
  module Ast
    class Ticker
      attr_reader :root, :current

      def initialize
        @current = Statement.new
      end

      def open
        current.name!
        opened.push(current)
        @current = Statement.new
      end

      def comma
        current.argument! if current.buffer?
        opened.last.merge(current)
        @current = Statement.new
      end

      def close
        current.argument! unless current.named?
        opened.last.merge(current)
        @current = opened.pop
      end

      # @param char [String]
      def char(char)
        current.print(char)
      end

      def opened
        @opened ||= []
      end
    end
  end
end


================================================
FILE: lib/click_house/ast.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Ast
    autoload :Statement, 'click_house/ast/statement'
    autoload :Ticker, 'click_house/ast/ticker'
    autoload :Parser, 'click_house/ast/parser'
  end
end


================================================
FILE: lib/click_house/benchmark/casting.rb
================================================
# frozen_string_literal: true

require 'bundler/setup'
require 'benchmark'
require 'pry'
require_relative '../../click_house'


ClickHouse.config.json_serializer = ClickHouse::Serializer::JsonOjSerializer
ClickHouse.config.json_parser = ClickHouse::Middleware::ParseJsonOj
ClickHouse.connection.drop_table('benchmark', if_exists: true)
ClickHouse.connection.execute <<~SQL
CREATE TABLE benchmark(
  int   Nullable(Int8),
  date  Nullable(Date),
  array Array(String),
  map   Map(String, IPv4)
) ENGINE Memory
SQL

INPUT = Array.new(200_000, {
  'int' => 21341234,
  'date' => Date.new(2022, 1, 1),
  'array' => ['foo'],
  'map' => {'ip' => IPAddr.new('127.0.0.1')}
})

Benchmark.bm do |x|
  x.report('insert: no casting') do
    ClickHouse.connection.insert('benchmark', INPUT)
  end

  x.report('insert: with casting') do
    schema = ClickHouse.connection.table_schema('benchmark')
    ClickHouse.connection.insert('benchmark', schema.serialize(INPUT))
  end

  x.report('select: no casting') do
    ClickHouse.connection.select_all('SELECT * FROM benchmark').data
  end

  x.report('select: with casting') do
    ClickHouse.connection.select_all('SELECT * FROM benchmark').to_a
  end
end


================================================
FILE: lib/click_house/benchmark/map_join.rb
================================================
# frozen_string_literal: true

require 'benchmark'
require 'stringio'

INPUT = Array.new(5_000_000, 'foo bar')

Benchmark.bm do |x|
  x.report('map.join') do
    INPUT.map(&:to_s).join("\n")
  end

  x.report('StringIO') do
    out = StringIO.new
    INPUT.each do |value|
      out << "#{value}\n"
    end
    out.string
  end
end


================================================
FILE: lib/click_house/config.rb
================================================
# frozen_string_literal: true

module ClickHouse
  class Config
    DEFAULTS = {
      adapter: Faraday.default_adapter,
      url: nil,
      scheme: 'http',
      host: 'localhost',
      port: '8123',
      logger: nil,
      database: nil,
      username: nil,
      password: nil,
      timeout: nil,
      open_timeout: nil,
      ssl_verify: false,
      headers: {},
      global_params: {},
      json_parser: ClickHouse::Middleware::ParseJson,
      json_serializer: ClickHouse::Serializer::JsonSerializer,
      oj_dump_options: {
        mode: :compat # to be able to dump improper JSON like {1 => 2}
      },
      oj_load_options: {
        mode: :custom,
        allow_blank: true,
        bigdecimal_as_decimal: false, # dump BigDecimal as a String
        bigdecimal_load: :bigdecimal, # convert all decimal numbers to BigDecimal
      },
      json_load_options: {
        decimal_class: BigDecimal,
      },
      # should be after json load options
      symbolize_keys: false,
    }.freeze

    attr_accessor :adapter
    attr_accessor :logger
    attr_accessor :scheme
    attr_accessor :host
    attr_accessor :port
    attr_accessor :database
    attr_accessor :url
    attr_accessor :username
    attr_accessor :password
    attr_accessor :timeout
    attr_accessor :open_timeout
    attr_accessor :ssl_verify
    attr_accessor :headers
    attr_accessor :global_params
    attr_accessor :oj_load_options
    attr_accessor :json_load_options
    attr_accessor :json_parser # response middleware
    attr_accessor :oj_dump_options
    attr_accessor :json_serializer # [ClickHouse::Serializer::Base]
    attr_accessor :symbolize_keys # [NilClass, Boolean]

    def initialize(params = {})
      assign(DEFAULTS.merge(params))
      yield(self) if block_given?
    end

    # @return [self]
    def assign(params = {})
      params.each { |k, v| public_send("#{k}=", v) }

      self
    end

    def auth?
      !username.nil? || !password.nil?
    end

    def logger!
      @logger || null_logger
    end

    def url!
      @url || "#{scheme}://#{host}:#{port}"
    end

    def null_logger
      @null_logger ||= Logger.new(IO::NULL)
    end

    # @param klass [ClickHouse::Serializer::Base]
    def json_serializer=(klass)
      @json_serializer = klass.new(self)
    end

    def symbolize_keys=(value)
      bool = value ? true : false

      # merge to be able to clone a config
      # prevent overriding default values
      self.oj_load_options = oj_load_options.merge(symbol_keys: bool)
      self.json_load_options = json_load_options.merge(symbolize_names: bool)
      @symbolize_keys = bool
    end

    # @param name [Symbol, String]
    def key(name)
      symbolize_keys ? name.to_sym : name.to_s
    end
  end
end


================================================
FILE: lib/click_house/connection.rb
================================================
# frozen_string_literal: true

module ClickHouse
  class Connection
    include Extend::ConnectionHealthy
    include Extend::ConnectionDatabase
    include Extend::ConnectionTable
    include Extend::ConnectionSelective
    include Extend::ConnectionInserting
    include Extend::ConnectionAltering
    include Extend::ConnectionExplaining

    attr_reader :config

    # @param [Config]
    def initialize(config)
      @config = config
    end

    def execute(query, body = nil, database: config.database, params: {})
      post(body, query: { query: query }, database: database, params: config.global_params.merge(params))
    end

    # @param path [String] Clickhouse HTTP endpoint, e.g. /ping, /replica_status
    # @param body [String] SQL to run
    # @param database [String|NilClass] database to use, nil to skip
    # @param query [Hash] other CH settings to send through params, e.g. max_rows_to_read=1
    # @example get(body: 'select number from system.numbers limit 100', query: { max_rows_to_read: 10 })
    # @return [Faraday::Response]
    def get(path = '/', body: '', query: {}, database: config.database)
      # backward compatibility since
      # https://github.com/shlima/click_house/pull/12/files#diff-9c6f3f06d3b575731eae4b6b95ddbcdcc20452c432b8f6e87a3a8e8645818107R24
      if query.is_a?(String)
        query = { query: query }
        config.logger!.warn('since v1.4.0 use connection.get(body: "SELECT 1") instead of connection.get(query: "SELECT 1")')
      end

      transport.get(path) do |conn|
        conn.params = query.merge(database: database).compact
        conn.params[:send_progress_in_http_headers] = 1 unless body.empty?
        conn.body = body
      end
    end

    def post(body = nil, query: {}, database: config.database, params: {})
      transport.post(compose('/', query.merge(database: database, **params)), body)
    end

    # transport should work the same both with Faraday v1 and Faraday v2
    # rubocop:disable Metrics/AbcSize
    def transport
      @transport ||= Faraday.new(config.url!) do |conn|
        conn.options.timeout = config.timeout
        conn.options.open_timeout = config.open_timeout
        conn.headers = config.headers
        conn.ssl.verify = config.ssl_verify

        if config.auth?
          if faraday_v1?
            conn.request :basic_auth, config.username, config.password
          else
            conn.request :authorization, :basic, config.username, config.password
          end
        end

        conn.response Middleware::RaiseError
        conn.response Middleware::Logging, logger: config.logger!
        conn.response Middleware::SummaryMiddleware, options: { config: config } # should be after logger
        conn.response config.json_parser, content_type: %r{application/json}, options: { config: config }
        conn.response Middleware::ParseCsv, content_type: %r{text/csv}, options: { config: config }
        conn.adapter config.adapter
      end
    end
    # rubocop:enable Metrics/AbcSize

    def compose(path, query = {})
      # without <query.compact> "DB::Exception: Empty query" error will occur
      "#{path}?#{URI.encode_www_form({ send_progress_in_http_headers: 1 }.merge(query).compact)}"
    end

    # @return [Boolean]
    def faraday_v1?
      Faraday::VERSION.start_with?('1')
    end
  end
end


================================================
FILE: lib/click_house/definition/column.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Definition
    class Column
      attr_accessor :name
      attr_accessor :type
      attr_accessor :nullable
      attr_accessor :low_cardinality
      attr_accessor :extensions
      attr_accessor :default
      attr_accessor :materialized
      attr_accessor :ttl

      def initialize(params = {})
        params.each { |k, v| public_send("#{k}=", v) }
        yield(self) if block_given?
      end

      def to_s
        type = extension_type
        type = "Nullable(#{type})" if nullable
        type = "LowCardinality(#{type})" if low_cardinality

        "#{name} #{type} #{opts}"
      end

      def opts
        options = {
          DEFAULT: Util::Statement.ensure(default, default),
          MATERIALIZED: Util::Statement.ensure(materialized, materialized),
          TTL: Util::Statement.ensure(ttl, ttl)
        }.compact

        result = options.each_with_object([]) do |(key, value), object|
          object << "#{key} #{value}"
        end

        result.join(' ')
      end

      def extension_type
        extensions.nil? ? type : format(type, *extensions)
      rescue TypeError, ArgumentError
        raise StandardError, "please provide extensions for <#{type}>"
      end
    end
  end
end


================================================
FILE: lib/click_house/definition/column_set.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Definition
    class ColumnSet
      TYPES = ClickHouse.types.each_with_object([]) do |(name, type), object|
        object << name.sub('%s', "'%s'") if type.ddl?
      end

      class << self
        # @input "DateTime('%s')"
        # @output "DateTime"
        def method_name_for_type(type)
          type.sub(/\(.+/, '')
        end
      end

      TYPES.each do |type|
        method_name = method_name_for_type(type)
        # t.Decimal :customer_id, nullable: true, default: ''
        # t.Decimal :money, 1, 2, nullable: true, default: ''
        class_eval <<-METHODS, __FILE__, __LINE__ + 1
          def #{method_name}(*definition)
            name = definition[0]
            extentions = []
            options = {}
            extensions = Array(definition[1..-1]).each do |el|
              el.is_a?(Hash) ? options.merge!(el) : extentions.push(el)
            end

            columns << Column.new(type: "#{type}", name: name, extensions: extensions, **options)
          end
        METHODS
      end

      def initialize
        yield(self) if block_given?
      end

      def columns
        @columns ||= []
      end

      def to_s
        <<~SQL
          ( #{columns.map(&:to_s).join(', ')} )
        SQL
      end

      # @example
      #   t.Nested :json do |n|
      #     n.UInt8 :city_id
      #   end
      def nested(name, &block)
        columns << "#{name} Nested #{ColumnSet.new(&block)}"
      end

      alias_method :Nested, :nested

      def push(sql)
        columns << sql
      end

      alias_method :<<, :push
    end
  end
end

__END__

data = ClickHouse::Definition::ColumnSet.new do |t|
  t << "words Enum('hello' = 1, 'world' = 2)"
end

puts data.to_s

data = ClickHouse::Definition::ColumnSet.new do |t|
  t.Decimal :money
  t.Float32 :client_id, default: 0
  t.Float32 :city_id, default: 0, nullable: true
  t.Nested :json do |n|
    n.Date :created_at
    n.Date :updated_at
  end

  t << "CUSTOM SQL"
end


================================================
FILE: lib/click_house/definition.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Definition
    autoload :Column, 'click_house/definition/column'
    autoload :ColumnSet, 'click_house/definition/column_set'
  end
end


================================================
FILE: lib/click_house/errors.rb
================================================
# frozen_string_literal: true

module ClickHouse
  Error = Class.new(StandardError)
  NetworkException = Class.new(Error)
  DbException = Class.new(Error)
  StatementException = Class.new(Error)
  SerializeError = Class.new(Error)
end


================================================
FILE: lib/click_house/extend/configurable.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Extend
    module Configurable
      def config(&block)
        yield(@config) if defined?(@config) && block

        @config ||= Config.new(&block)
      end
    end
  end
end


================================================
FILE: lib/click_house/extend/connectible.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Extend
    module Connectible
      def connection=(connection)
        @connection = connection
      end

      def connection
        @connection ||= Connection.new(config.clone)
      end
    end
  end
end


================================================
FILE: lib/click_house/extend/connection_altering.rb
================================================
# frozen_string_literal: true

# rubocop:disable Metrics/ParameterLists
module ClickHouse
  module Extend
    module ConnectionAltering
      def add_column(table, name, type, default: nil, if_not_exists: false, after: nil, cluster: nil)
        sql = 'ADD COLUMN %<exists>s %<name>s %<type>s %<default>s %<after>s'

        pattern = {
          name: name,
          exists: Util::Statement.ensure(if_not_exists, 'IF NOT EXISTS'),
          type: type,
          default: Util::Statement.ensure(default, "DEFAULT #{default}"),
          after: Util::Statement.ensure(after, "AFTER #{after}")
        }

        alter_table(table, format(sql, pattern), cluster: cluster)
      end

      def drop_column(table, name, if_exists: false, cluster: nil)
        sql = 'DROP COLUMN %<exists>s %<name>s'

        pattern = {
          name: name,
          exists: Util::Statement.ensure(if_exists, 'IF EXISTS')
        }

        alter_table(table, format(sql, pattern), cluster: cluster)
      end

      def clear_column(table, name, partition:, if_exists: false, cluster: nil)
        sql = 'CLEAR COLUMN %<exists>s %<name>s %<partition>s'

        pattern = {
          name: name,
          exists: Util::Statement.ensure(if_exists, 'IF EXISTS'),
          partition: "IN PARTITION #{partition}"
        }

        alter_table(table, format(sql, pattern), cluster: cluster)
      end

      def modify_column(table, name, type: nil, default: nil, if_exists: false, cluster: nil)
        sql = 'MODIFY COLUMN %<exists>s %<name>s %<type>s %<default>s'

        pattern = {
          name: name,
          type: type,
          exists: Util::Statement.ensure(if_exists, 'IF EXISTS'),
          default: Util::Statement.ensure(default, "DEFAULT #{default}")
        }

        alter_table(table, format(sql, pattern), cluster: cluster)
      end

      def alter_table(name, sql = nil, cluster: nil)
        template = 'ALTER TABLE %<name>s %<cluster>s %<sql>s'
        sql = yield(sql) if sql.nil?

        pattern = {
          name: name,
          sql: sql,
          cluster: Util::Statement.ensure(cluster, "ON CLUSTER #{cluster}"),
        }

        execute(format(template, pattern)).success?
      end

      def add_index(
        table_name,
        name,
        expression,
        type:,
        granularity: nil,
        after: nil,
        cluster: nil
      )
        template = 'ADD INDEX %<name>s %<expression>s TYPE %<type>s GRANULARITY %<granularity>d %<after>s'
        pattern = {
          name: name,
          expression: expression,
          type: type,
          granularity: granularity,
          after: Util::Statement.ensure(after, "AFTER #{after}"),
        }

        alter_table(table_name, format(template, pattern), cluster: cluster)
      end

      def drop_index(table_name, name, cluster: nil)
        alter_table(table_name, <<~SQL, cluster: cluster)
          DROP INDEX #{name}
        SQL
      end
    end
  end
end
# rubocop:enable Metrics/ParameterLists


================================================
FILE: lib/click_house/extend/connection_database.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Extend
    module ConnectionDatabase
      # @return [Array<String>]
      def databases
        Array(execute('SHOW DATABASES FORMAT CSV', database: nil).body).tap(&:flatten!)
      end

      def create_database(name, if_not_exists: false, cluster: nil, engine: nil)
        sql = 'CREATE DATABASE %<exists>s %<name>s %<cluster>s %<engine>s'

        pattern = {
          name: name,
          exists: Util::Statement.ensure(if_not_exists, 'IF NOT EXISTS'),
          cluster: Util::Statement.ensure(cluster, "ON CLUSTER #{cluster}"),
          engine: Util::Statement.ensure(engine, "ENGINE = #{engine}")
        }

        execute(format(sql, pattern), database: nil).success?
      end

      def drop_database(name, if_exists: false, cluster: nil)
        sql = 'DROP DATABASE %<exists>s %<name>s %<cluster>s'

        pattern = {
          name: name,
          exists: Util::Statement.ensure(if_exists, 'IF EXISTS'),
          cluster: Util::Statement.ensure(cluster, "ON CLUSTER #{cluster}"),
        }

        execute(format(sql, pattern), database: nil).success?
      end
    end
  end
end


================================================
FILE: lib/click_house/extend/connection_explaining.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Extend
    module ConnectionExplaining
      EXPLAIN = 'EXPLAIN'
      EXPLAIN_RE = /\A(\s*#{EXPLAIN})/io.freeze

      # @return String
      def explain(sql, io: StringIO.new)
        res = execute("#{EXPLAIN} #{sql.gsub(EXPLAIN_RE, '')}")
        io.puts(res.body)
        io.string
      end
    end
  end
end


================================================
FILE: lib/click_house/extend/connection_healthy.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Extend
    module ConnectionHealthy
      def ping
        get('/ping', database: nil).success?
      end

      def replicas_status
        get('/replicas_status', database: nil).success?
      end
    end
  end
end


================================================
FILE: lib/click_house/extend/connection_inserting.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Extend
    module ConnectionInserting
      DEFAULT_JSON_EACH_ROW_FORMAT = 'JSONEachRow'
      DEFAULT_JSON_COMPACT_EACH_ROW_FORMAT = 'JSONCompactEachRow'

      # @return [Boolean]
      #
      # == Example with a block
      # insert('rspec', columns: %i[name id]) do |buffer|
      #   buffer << ['Sun', 1]
      #   buffer << ['Moon', 2]
      # end
      #
      # @return [Response::Execution]
      # @param body [Array, Hash]
      # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
      def insert(table, body = [], **opts)
        # In Ruby < 3.0, if the last argument is a hash, and the method being called
        # accepts keyword arguments, then it is always converted to keyword arguments.
        columns = opts.fetch(:columns, [])
        values =  opts.fetch(:values, [])
        format = opts.fetch(:format, nil)

        yield(body) if block_given?

        # values: [{id: 1}]
        if values.any? && columns.empty?
          return insert_rows(table, values, format: format)
        end

        # body: [{id: 1}]
        if body.any? && columns.empty?
          return insert_rows(table, body, format: format)
        end

        # body: [1], columns: ["id"]
        if body.any? && columns.any?
          return insert_compact(table, columns: columns, values: body, format: format)
        end

        # columns: ["id"], values: [[1]]
        if columns.any? && values.any?
          return insert_compact(table, columns: columns, values: values, format: format)
        end

        Response::Factory.empty_exec(config)
      end
      # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity

      # @param table [String]
      # @param body [Array, Hash]
      # @param format [String]
      # @return [Response::Execution]
      #
      # Sometimes it's needed to use other format than JSONEachRow
      # For example if you want to send BigDecimal's you could use
      # JSONStringsEachRow format so string representation of BigDecimal will be parsed
      def insert_rows(table, body, format: nil)
        format ||= DEFAULT_JSON_EACH_ROW_FORMAT

        case body
        when Hash
          Response::Factory.exec(execute("INSERT INTO #{table} FORMAT #{format}", config.json_serializer.dump(body)))
        when Array
          Response::Factory.exec(execute("INSERT INTO #{table} FORMAT #{format}", config.json_serializer.dump_each_row(body)))
        else
          raise ArgumentError, "unknown body class <#{body.class}>"
        end
      end

      # @return [Response::Execution]
      def insert_compact(table, columns: [], values: [], format: nil)
        format ||= DEFAULT_JSON_COMPACT_EACH_ROW_FORMAT

        yield(values) if block_given?

        response = execute("INSERT INTO #{table} (#{columns.join(',')}) FORMAT #{format}", config.json_serializer.dump_each_row(values))
        Response::Factory.exec(response)
      end
    end
  end
end


================================================
FILE: lib/click_house/extend/connection_selective.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Extend
    module ConnectionSelective
      # @return [ResultSet]
      def select_all(sql)
        response = get(body: sql, query: { default_format: 'JSON' })
        Response::Factory.response(response, config)
      end

      def select_value(sql)
        response = get(body: sql, query: { default_format: 'JSON' })
        got = Response::Factory.response(response, config).first

        case got
        when Hash
          Array(got).dig(0, -1) # get a value of a first key for JSON format
        when Array
          got[0] # for CSV format
        else
          got # for RowBinary format
        end
      end

      def select_one(sql)
        response = get(body: sql, query: { default_format: 'JSON' })
        Response::Factory.response(response, config).first
      end
    end
  end
end


================================================
FILE: lib/click_house/extend/connection_table.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Extend
    module ConnectionTable
      # @return [Array<String>]
      def tables
        Array(execute('SHOW TABLES FORMAT CSV').body).tap(&:flatten!)
      end

      # @return [ResultSet]
      def describe_table(name)
        Response::Factory.response(execute("DESCRIBE TABLE #{name} FORMAT JSON"), config)
      end

      # @return [ResultSet]
      def table_schema(name)
        Response::Factory.response(execute("SELECT * FROM #{name} WHERE 1=0 FORMAT JSON"), config)
      end

      # @return [Boolean]
      def table_exists?(name, temporary: false)
        sql = 'EXISTS %<temporary>s TABLE  %<name>s FORMAT CSV'

        pattern = {
          name: name,
          temporary: Util::Statement.ensure(temporary, 'TEMPORARY')
        }

        Type::BooleanType.new.cast(execute(format(sql, pattern)).body.dig(0, 0))
      end

      def drop_table(name, temporary: false, if_exists: false, cluster: nil)
        sql = 'DROP %<temporary>s TABLE %<exists>s %<name>s %<cluster>s'

        pattern = {
          name: name,
          temporary: Util::Statement.ensure(temporary, 'TEMPORARY'),
          exists: Util::Statement.ensure(if_exists, 'IF EXISTS'),
          cluster: Util::Statement.ensure(cluster, "ON CLUSTER #{cluster}"),
        }

        execute(format(sql, pattern)).success?
      end

      # rubocop:disable Metrics/ParameterLists
      def create_table(
        name,
        if_not_exists: false, cluster: nil,
        partition: nil, order: nil, primary_key: nil, sample: nil, ttl: nil, settings: nil,
        engine:,
        &block
      )
        sql = <<~SQL
          CREATE TABLE %<exists>s %<name>s %<cluster>s %<definition>s %<engine>s
            %<partition>s
            %<order>s
            %<primary_key>s
            %<sample>s
            %<ttl>s
            %<settings>s
        SQL
        definition = ClickHouse::Definition::ColumnSet.new(&block)

        pattern = {
          name: name,
          exists: Util::Statement.ensure(if_not_exists, 'IF NOT EXISTS'),
          definition: definition.to_s,
          cluster: Util::Statement.ensure(cluster, "ON CLUSTER #{cluster}"),
          partition: Util::Statement.ensure(partition, "PARTITION BY #{partition}"),
          order: Util::Statement.ensure(order, "ORDER BY #{order}"),
          primary_key: Util::Statement.ensure(primary_key, "PRIMARY KEY #{primary_key}"),
          sample: Util::Statement.ensure(sample, "SAMPLE BY  #{sample}"),
          ttl: Util::Statement.ensure(ttl, "TTL #{ttl}"),
          settings: Util::Statement.ensure(settings, "SETTINGS #{settings}"),
          engine: Util::Statement.ensure(engine, "ENGINE = #{engine}")
        }

        execute(format(sql, pattern)).success?
      end
      # rubocop:enable Metrics/ParameterLists

      def truncate_table(name, if_exists: false, cluster: nil)
        sql = 'TRUNCATE TABLE %<exists>s %<name>s %<cluster>s'

        pattern = {
          name: name,
          exists: Util::Statement.ensure(if_exists, 'IF EXISTS'),
          cluster: Util::Statement.ensure(cluster, "ON CLUSTER #{cluster}")
        }

        execute(format(sql, pattern)).success?
      end

      def truncate_tables(names = tables, *argv)
        Array(names).each { |name| truncate_table(name, *argv) }
      end

      def rename_table(from, to, cluster: nil)
        from = Array(from)
        to = Array(to)

        unless from.length == to.length
          raise StatementException, '<from> tables length should equal <to> length'
        end

        sql = <<~SQL
          RENAME TABLE %<names>s %<cluster>s
        SQL

        pattern = {
          names: from.zip(to).map { |a| a.join(' TO ') }.join(', '),
          cluster: Util::Statement.ensure(cluster, "ON CLUSTER #{cluster}")
        }

        execute(format(sql, pattern)).success?
      end
    end
  end
end


================================================
FILE: lib/click_house/extend/type_definition.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Extend
    module TypeDefinition
      def types
        @types ||= Hash.new(Type::UndefinedType.new)
      end

      def add_type(type, klass)
        types[type] = klass
      end
    end
  end
end


================================================
FILE: lib/click_house/extend.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Extend
    autoload :TypeDefinition, 'click_house/extend/type_definition'
    autoload :Configurable, 'click_house/extend/configurable'
    autoload :Connectible, 'click_house/extend/connectible'
    autoload :ConnectionHealthy, 'click_house/extend/connection_healthy'
    autoload :ConnectionDatabase, 'click_house/extend/connection_database'
    autoload :ConnectionTable, 'click_house/extend/connection_table'
    autoload :ConnectionSelective, 'click_house/extend/connection_selective'
    autoload :ConnectionInserting, 'click_house/extend/connection_inserting'
    autoload :ConnectionAltering, 'click_house/extend/connection_altering'
    autoload :ConnectionExplaining, 'click_house/extend/connection_explaining'
  end
end


================================================
FILE: lib/click_house/middleware/logging.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Middleware
    class Logging < Faraday::Middleware
      Faraday::Response.register_middleware self => self

      EMPTY = ''
      GET = :get

      attr_reader :logger, :starting

      def initialize(app = nil, logger:)
        @logger = logger
        super(app)
      end

      def call(env)
        @starting = timestamp
        super
      end

      # rubocop:disable Layout/LineLength
      def on_complete(env)
        summary = SummaryMiddleware.extract(env)
        logger.info("\e[1mSQL (#{duration_stats_log(summary)})\e[0m #{query(env)};")
        logger.debug(env.request_body) if log_body?(env)
        logger.info("\e[1mRead: #{summary.read_rows} rows, #{summary.read_bytes_pretty}. Written: #{summary.written_rows} rows, #{summary.written_bytes_pretty}\e[0m")
      end
      # rubocop:enable Layout/LineLength

      private

      def duration
        timestamp - starting
      end

      def timestamp
        Process.clock_gettime(Process::CLOCK_MONOTONIC)
      end

      # @return [Boolean]
      def log_body?(env)
        return unless logger.debug?
        return if env.method == GET # GET queries logs body as a statement
        return if env.request_body.nil? || env.request_body == EMPTY

        true
      end

      def query(env)
        if env.method == GET
          env.request_body
        else
          String(CGI.parse(env.url.query.to_s).dig('query', 0) || '[NO QUERY]').chomp
        end
      end

      def duration_stats_log(summary)
        "Total: #{Util::Pretty.measure(duration * 1000)}, CH: #{summary.elapsed_pretty}"
      end
    end
  end
end


================================================
FILE: lib/click_house/middleware/parse_csv.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Middleware
    class ParseCsv < ResponseBase
      Faraday::Response.register_middleware self => self

      # @param env [Faraday::Env]
      def on_complete(env)
        return unless content_type?(env, content_type)

        env.body = env.body.strip.empty? ? nil : CSV.parse(env.body)
      end
    end
  end
end


================================================
FILE: lib/click_house/middleware/parse_json.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Middleware
    class ParseJson < ResponseBase
      Faraday::Response.register_middleware self => self

      # @param env [Faraday::Env]
      def on_complete(env)
        return unless content_type?(env, content_type)

        env.body = JSON.parse(env.body, config.json_load_options) unless env.body.strip.empty?
      end
    end
  end
end


================================================
FILE: lib/click_house/middleware/parse_json_oj.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Middleware
    class ParseJsonOj < ResponseBase
      Faraday::Response.register_middleware self => self

      # @param env [Faraday::Env]
      def on_complete(env)
        return unless content_type?(env, content_type)

        env.body = Oj.load(env.body, config.oj_load_options) unless env.body.strip.empty?
      end

      private

      def on_setup
        require 'oj' unless defined?(Oj)
      end
    end
  end
end


================================================
FILE: lib/click_house/middleware/raise_error.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Middleware
    class RaiseError < Faraday::Middleware
      EXCEPTION_CODE_HEADER = 'x-clickhouse-exception-code'

      Faraday::Response.register_middleware self => self

      # @param env [Faraday::Env]
      def call(env)
        super
      rescue Faraday::ConnectionFailed => e
        raise NetworkException, e.message, e.backtrace
      end

      # @param env [Faraday::Env]
      def on_complete(env)
        if env.response_headers.include?(EXCEPTION_CODE_HEADER) || !env.success?
          raise DbException, "[#{env.status}] #{env.body}"
        end
      end
    end
  end
end


================================================
FILE: lib/click_house/middleware/response_base.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Middleware
    class ResponseBase < Faraday::Middleware
      CONTENT_TYPE_HEADER = 'content-type'

      attr_reader :options
      attr_reader :content_type

      def initialize(app = nil, options: {}, content_type: nil, preserve_raw: false)
        super(app)
        @options = options
        @content_type = content_type
        @preserve_raw = preserve_raw
        on_setup
      end

      # @return [Boolean]
      # @param env [Faraday::Env]
      # @param regexp [NilClass, Regexp]
      def content_type?(env, regexp)
        case regexp
        when NilClass
          false
        when Regexp
          regexp.match?(String(env[:response_headers][CONTENT_TYPE_HEADER]))
        else
          raise ArgumentError, "expected regexp got #{regexp.class}"
        end
      end

      # @return [Config]
      def config
        options.fetch(:config)
      end

      private

      def on_setup
        # require external dependencies here
      end
    end
  end
end


================================================
FILE: lib/click_house/middleware/summary_middleware.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Middleware
    class SummaryMiddleware < ResponseBase
      Faraday::Response.register_middleware self => self

      KEY = :summary

      # @param env [Faraday::Env]
      # @return [Response::Summary]
      def self.extract(env)
        env.custom_members.fetch(KEY)
      end

      # @param env [Faraday::Env]
      def on_complete(env)
        env.custom_members[KEY] = Response::Summary.new(
          config,
          headers: env.response_headers,
          body: env.body.is_a?(Hash) ? env.body : {}
        )
      end
    end
  end
end


================================================
FILE: lib/click_house/middleware.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Middleware
    autoload :ResponseBase, 'click_house/middleware/response_base'
    autoload :SummaryMiddleware, 'click_house/middleware/summary_middleware'
    autoload :Logging, 'click_house/middleware/logging'
    autoload :ParseCsv, 'click_house/middleware/parse_csv'
    autoload :ParseJsonOj, 'click_house/middleware/parse_json_oj'
    autoload :ParseJson, 'click_house/middleware/parse_json'
    autoload :RaiseError, 'click_house/middleware/raise_error'
  end
end


================================================
FILE: lib/click_house/response/factory.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Response
    class Factory
      KEY_META = 'meta'
      KEY_DATA = 'data'

      # @return [ResultSet]
      # @params faraday [Faraday::Response]
      # @params config [Config]
      def self.response(faraday, config)
        body = faraday.body

        # wrap to be able to use connection#select_one, connection#select_value
        # with other formats like binary
        return raw(faraday, config) unless body.is_a?(Hash)
        return raw(faraday, config) unless body.key?(config.key(KEY_META)) && body.key?(config.key(KEY_DATA))

        ResultSet.new(
          config: config,
          meta: body.fetch(config.key(KEY_META)),
          data: body.fetch(config.key(KEY_DATA)),
          summary: Middleware::SummaryMiddleware.extract(faraday.env)
        )
      end

      # @return [ResultSet]
      # Rae ResultSet (without type casting)
      def self.raw(faraday, config)
        ResultSet.raw(
          config: config,
          data: Util.array(faraday.body),
          summary: Middleware::SummaryMiddleware.extract(faraday.env)
        )
      end

      # Result of execution
      # @return [Response::Summary]
      # @params faraday [Faraday::Response]
      def self.exec(faraday)
        Middleware::SummaryMiddleware.extract(faraday.env)
      end

      # @return [Response::Summary]
      def self.empty_exec(config)
        Summary.new(config)
      end
    end
  end
end


================================================
FILE: lib/click_house/response/result_set.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Response
    class ResultSet
      extend Forwardable
      include Enumerable

      KEY_META_NAME = 'name'
      KEY_META_TYPE = 'type'

      def_delegators :to_a,
                     :inspect, :each, :fetch, :length, :count, :size,
                     :first, :last, :[], :to_h

      def_delegators :summary,
                     :statistics, :headers,
                     :totals, :rows_before_limit_at_least

      attr_reader :config, :meta, :data, :summary

      class << self
        # @param config [Config]
        # @return [ResultSet]
        def raw(config:, data:, summary:)
          new(config: config, data: data, to_a: data, meta: [], summary: summary)
        end
      end

      # @param config [Config]
      # @param meta [Array]
      # @param data [Array]
      # @param summary [Response::Summary]
      def initialize(config:, meta:, data:, summary:, to_a: nil)
        @config = config
        @meta = meta
        @data = data
        @summary = summary
        @to_a = to_a
      end

      # @return [Array, Hash]
      # @param data [Array, Hash]
      def serialize(data)
        case data
        when Hash
          serialize_one(data)
        when Array
          data.map(&method(:serialize_one))
        else
          raise ArgumentError, "expect Hash or Array, got: #{data.class}"
        end
      end

      # @return [Hash]
      # @param row [Hash]
      def serialize_one(row)
        row.each_with_object({}) do |(key, value), object|
          object[key] = serialize_column(key, value)
        end
      end

      # @param name [String] column name
      # @param value [Any]
      def serialize_column(name, value)
        stmt = types.fetch(name)
        serialize_type(stmt, value)
      rescue KeyError => e
        raise SerializeError, "field <#{name}> does not exists in table schema: #{types}", e.backtrace
      rescue StandardError => e
        raise SerializeError, "failed to serialize <#{name}> with #{stmt}, #{e.class}, #{e.message}", e.backtrace
      end

      def to_a
        @to_a ||= data.each do |row|
          row.each do |name, value|
            row[name] = cast_type(types.fetch(name), value)
          end
        end
      end

      # @return [Hash<String, Ast::Statement>]
      def types
        @types ||= meta.each_with_object({}) do |row, object|
          column = row.fetch(config.key(KEY_META_NAME))
          # make symbol keys, if config.symbolize_keys is true,
          # to be able to cast and serialize properly
          object[config.key(column)] = begin
            current = Ast::Parser.new(row.fetch(config.key(KEY_META_TYPE))).parse
            assign_type(current)
            current
          end
        end
      end

      private

      # @param stmt [Ast::Statement]
      def assign_type(stmt)
        stmt.caster = ClickHouse.types[stmt.name]

        if stmt.caster.is_a?(Type::UndefinedType)
          placeholders = stmt.arguments.map(&:placeholder)
          stmt.caster = ClickHouse.types["#{stmt.name}(#{placeholders.join(', ')})"]
        end

        stmt.arguments.each(&method(:assign_type))
      end

      # @param stmt [Ast::Statement]
      def cast_type(stmt, value)
        return cast_container(stmt, value) if stmt.caster.container?
        return cast_map(stmt, Hash(value)) if stmt.caster.map?
        return cast_tuple(stmt, Array(value)) if stmt.caster.tuple?

        stmt.caster.cast(value, *stmt.argument_values)
      end

      # @return [Hash]
      # @param stmt [Ast::Statement]
      # @param hash [Hash]
      def cast_map(stmt, hash)
        raise ArgumentError, "expect hash got #{hash.class}" unless hash.is_a?(Hash)

        key_type, value_type = stmt.arguments
        hash.each_with_object({}) do |(key, value), object|
          object[cast_type(key_type, key)] = cast_type(value_type, value)
        end
      end

      # @param stmt [Ast::Statement]
      def cast_container(stmt, value)
        stmt.caster.cast_each(value) do |item|
          cast_type(stmt.argument_first!, item)
        end
      end

      # @param stmt [Ast::Statement]
      def cast_tuple(stmt, value)
        value.map.with_index do |item, ix|
          cast_type(stmt.arguments.fetch(ix), item)
        end
      end

      # @param stmt [Ast::Statement]
      def serialize_type(stmt, value)
        return serialize_container(stmt, value) if stmt.caster.container?
        return serialize_map(stmt, value) if stmt.caster.map?
        return serialize_tuple(stmt, Array(value)) if stmt.caster.tuple?

        stmt.caster.serialize(value, *stmt.argument_values)
      end

      # @param stmt [Ast::Statement]
      def serialize_container(stmt, value)
        stmt.caster.serialize_each(value) do |item|
          serialize_type(stmt.argument_first!, item)
        end
      end

      # @return [Hash]
      # @param stmt [Ast::Statement]
      # @param hash [Hash]
      def serialize_map(stmt, hash)
        raise ArgumentError, "expect hash got #{hash.class}" unless hash.is_a?(Hash)

        key_type, value_type = stmt.arguments
        hash.each_with_object({}) do |(key, value), object|
          object[serialize_type(key_type, key)] = serialize_type(value_type, value)
        end
      end

      # @param stmt [Ast::Statement]
      def serialize_tuple(stmt, value)
        value.map.with_index do |item, ix|
          serialize_type(stmt.arguments.fetch(ix), item)
        end
      end
    end
  end
end


================================================
FILE: lib/click_house/response/summary.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Response
    class Summary
      SUMMARY_HEADER = 'x-clickhouse-summary'
      KEY_TOTALS = 'totals'
      KEY_STATISTICS = 'statistics'
      KEY_ROWS_BEFORE_LIMIT_AT_LEAST = 'rows_before_limit_at_least'
      KEY_STAT_ELAPSED = 'elapsed'

      attr_reader :config,
                  :headers,
                  :summary,
                  # {:elapsed=>0.387287e-3, :rows_read=>0, :bytes_read=>0}}
                  :statistics,
                  :totals,
                  :rows_before_limit_at_least

      # @param config [Config]
      # @param headers [Faraday::Utils::Headers]
      # @param body [Hash]
      # TOTALS [Array|Hash|NilClass] Support for 'GROUP BY WITH TOTALS' modifier
      #   https://clickhouse.tech/docs/en/sql-reference/statements/select/group-by/#with-totals-modifier
      #   Hash in JSON format and Array in JSONCompact
      def initialize(config, headers: Faraday::Utils::Headers.new, body: {})
        @headers = headers
        @config = config
        @statistics = body.fetch(config.key(KEY_STATISTICS), {})
        @totals = body[config.key(KEY_TOTALS)]
        @rows_before_limit_at_least = body[config.key(KEY_ROWS_BEFORE_LIMIT_AT_LEAST)]
        @summary = parse_summary(headers[SUMMARY_HEADER])
      end

      # @return [Integer]
      def read_rows
        summary[config.key('read_rows')].to_i
      end

      # @return [Integer]
      def read_bytes
        summary[config.key('read_bytes')].to_i
      end

      # @return [String]
      def read_bytes_pretty
        Util::Pretty.size(read_bytes)
      end

      # @return [Integer]
      def written_rows
        summary[config.key('written_rows')].to_i
      end

      # @return [Integer]
      def written_bytes
        summary[config.key('written_bytes')].to_i
      end

      # @return [String]
      def written_bytes_pretty
        Util::Pretty.size(written_bytes)
      end

      # @return [Integer]
      def total_rows_to_read
        summary[config.key('total_rows_to_read')].to_i
      end

      # @return [Integer]
      def result_rows
        summary[config.key('result_rows')].to_i
      end

      # @return [Integer]
      def result_bytes
        summary[config.key('result_bytes')].to_i
      end

      # @return [Float]
      def elapsed
        statistics[config.key(KEY_STAT_ELAPSED)].to_f
      end

      # @return [String]
      def elapsed_pretty
        Util::Pretty.measure(elapsed * 1000)
      end

      private

      # @return [Hash]
      # {
      #   "read_rows" => "1",
      #   "read_bytes" => "23",
      #   "written_rows" => "1",
      #   "written_bytes" => "23",
      #   "total_rows_to_read" => "0",
      #   "result_rows" => "1",
      #   "result_bytes" => "23",
      # }
      def parse_summary(value)
        return {} if value.nil? || value.empty?

        JSON.parse(value)
      end
    end
  end
end


================================================
FILE: lib/click_house/response.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Response
    autoload :Factory, 'click_house/response/factory'
    autoload :ResultSet, 'click_house/response/result_set'
    autoload :Summary, 'click_house/response/summary'
  end
end


================================================
FILE: lib/click_house/serializer/base.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Serializer
    class Base
      attr_reader :config

      # @param config [Config]
      def initialize(config)
        @config = config
        on_setup
      end

      def dump(data)
        raise NotImplementedError, __method__
      end

      # @return [String]
      # @param data [Array]
      def dump_each_row(data, sep = "\n")
        data.map(&method(:dump)).join(sep)
      end

      private

      # require external dependencies here
      def on_setup
        nil
      end
    end
  end
end


================================================
FILE: lib/click_house/serializer/json_oj_serializer.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Serializer
    class JsonOjSerializer < Base
      def dump(data)
        Oj.dump(data, config.oj_dump_options)
      end

      private

      def on_setup
        require 'oj' unless defined?(Oj)
      end
    end
  end
end


================================================
FILE: lib/click_house/serializer/json_serializer.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Serializer
    class JsonSerializer < Base
      def dump(data)
        JSON.dump(data)
      end
    end
  end
end


================================================
FILE: lib/click_house/serializer.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Serializer
    autoload :Base, 'click_house/serializer/base'
    autoload :JsonSerializer, 'click_house/serializer/json_serializer'
    autoload :JsonOjSerializer, 'click_house/serializer/json_oj_serializer'
  end
end


================================================
FILE: lib/click_house/type/array_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class ArrayType < BaseType
      def cast_each(value, *_argv, &block)
        value.map(&block)
      end

      def serialize_each(value, *_argv, &block)
        value.map(&block)
      end

      def container?
        true
      end

      def ddl?
        false
      end
    end
  end
end


================================================
FILE: lib/click_house/type/base_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class BaseType
      def cast(_value, *)
        raise NotImplementedError, __method__
      end

      def cast_each(_value, *)
        raise NotImplementedError, __method__
      end

      def serialize_each(_value, *)
        raise NotImplementedError, __method__
      end

      # @return [Boolean]
      # true if type contains another type like Nullable(T) or Array(T)
      def container?
        false
      end

      # @return [Boolean]
      # true if type is a Map
      def map?
        false
      end

      # @return [Boolean]
      # true if type is a Tuple
      def tuple?
        false
      end

      # @return [Boolean]
      # skip type from DDL statements
      def ddl?
        true
      end
    end
  end
end


================================================
FILE: lib/click_house/type/boolean_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class BooleanType < BaseType
      TRUE_VALUE = 1
      FALSE_VALUE = 0

      def cast(value)
        case value
        when TrueClass, FalseClass
          value
        else
          value.to_i == TRUE_VALUE
        end
      end

      def serialize(value)
        value ? TRUE_VALUE : FALSE_VALUE
      end
    end
  end
end


================================================
FILE: lib/click_house/type/date_time64_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class DateTime64Type < BaseType
      BASE_FORMAT = '%Y-%m-%d %H:%M:%S'
      CAST_FORMAT = "#{BASE_FORMAT}.%N"
      SERIALIZE_FORMATS = {
        0 => BASE_FORMAT,
        1 => "#{BASE_FORMAT}.%1N",
        2 => "#{BASE_FORMAT}.%2N",
        3 => "#{BASE_FORMAT}.%3N",
        4 => "#{BASE_FORMAT}.%4N",
        5 => "#{BASE_FORMAT}.%5N",
        6 => "#{BASE_FORMAT}.%6N",
        7 => "#{BASE_FORMAT}.%7N",
        8 => "#{BASE_FORMAT}.%8N",
        9 => "#{BASE_FORMAT}.%9N",
      }.freeze

      # Tick size (precision):
      #   10-precision seconds.
      #   Valid range: [ 0 : 9 ].
      #   Typically are used - 3 (milliseconds), 6 (microseconds), 9 (nanoseconds).
      def cast(value, precision = 0, tz = nil)
        format = precision.zero? ? BASE_FORMAT : CAST_FORMAT

        if tz
          Time.find_zone(tz).strptime(value, format)
        else
          Time.strptime(value, format)
        end
      end

      def serialize(value, precision = 3, _tz = nil)
        value.strftime(SERIALIZE_FORMATS.fetch(precision))
      end
    end
  end
end


================================================
FILE: lib/click_house/type/date_time_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class DateTimeType < BaseType
      FORMAT = '%Y-%m-%d %H:%M:%S'

      def cast(value, tz = nil)
        if tz
          Time.find_zone(tz).strptime(value, FORMAT)
        else
          Time.strptime(value, FORMAT)
        end
      end

      def serialize(value, *)
        value.strftime(FORMAT)
      end
    end
  end
end


================================================
FILE: lib/click_house/type/date_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class DateType < BaseType
      FORMAT = '%Y-%m-%d'

      def cast(value)
        Date.strptime(value, FORMAT)
      end

      def serialize(value)
        value.strftime(FORMAT)
      end
    end
  end
end


================================================
FILE: lib/click_house/type/decimal_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class DecimalType < BaseType
      MAXIMUM = Float::DIG.next

      # clickhouse:
      # P - precision. Valid range: [ 1 : 76 ]. Determines how many decimal digits number can have (including fraction).
      # S - scale. Valid range: [ 0 : P ]. Determines how many decimal digits fraction can have.
      #
      # when Oj parser @refs https://stackoverflow.com/questions/47885304/deserialise-json-numbers-as-bigdecimal
      def cast(value, precision = MAXIMUM, _scale = nil)
        case value
        when BigDecimal
          value
        when String
          BigDecimal(value)
        else
          BigDecimal(value, precision > MAXIMUM ? MAXIMUM : precision)
        end
      end

      # @return [BigDecimal]
      def serialize(value, precision = MAXIMUM, _scale = nil)
        cast(value, precision)
      end
    end
  end
end


================================================
FILE: lib/click_house/type/fixed_string_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class FixedStringType < BaseType
      def cast(value, _limit = nil)
        value.to_s
      end

      def serialize(value, limit = nil)
        value[0..(limit ? limit.pred : -1)] unless value.nil?
      end
    end
  end
end


================================================
FILE: lib/click_house/type/float_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class FloatType < BaseType
      def cast(value)
        Float(value) unless value.nil?
      end

      def serialize(value)
        value.to_f unless value.nil?
      end
    end
  end
end


================================================
FILE: lib/click_house/type/integer_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class IntegerType < BaseType
      def cast(value)
        Integer(value)
      end

      def serialize(value)
        value.to_i
      end
    end
  end
end


================================================
FILE: lib/click_house/type/ip_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class IPType < BaseType
      def cast(value)
        IPAddr.new(value)
      end

      def serialize(value)
        value.to_s
      end
    end
  end
end


================================================
FILE: lib/click_house/type/low_cardinality_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class LowCardinalityType < BaseType
      def cast_each(value, *_argv)
        yield(value)
      end

      def serialize_each(value, *_argv)
        yield(value)
      end

      def container?
        true
      end
    end
  end
end


================================================
FILE: lib/click_house/type/map_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class MapType < BaseType
      def map?
        true
      end

      def ddl?
        false
      end
    end
  end
end


================================================
FILE: lib/click_house/type/nullable_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class NullableType < BaseType
      def cast_each(value, *_argv)
        yield(value) unless value.nil?
      end

      def serialize_each(value, *_argv)
        yield(value) unless value.nil?
      end

      def container?
        true
      end

      def ddl?
        false
      end
    end
  end
end


================================================
FILE: lib/click_house/type/string_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class StringType < BaseType
      def cast(value, *)
        value.to_s
      end

      def serialize(value, *)
        value.to_s
      end
    end
  end
end


================================================
FILE: lib/click_house/type/tuple_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class TupleType < BaseType
      def tuple?
        true
      end

      def ddl?
        false
      end
    end
  end
end


================================================
FILE: lib/click_house/type/undefined_type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    class UndefinedType < BaseType
      def cast(value, *)
        value
      end

      def serialize(value, *)
        value
      end
    end
  end
end


================================================
FILE: lib/click_house/type.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Type
    autoload :BaseType, 'click_house/type/base_type'
    autoload :NullableType, 'click_house/type/nullable_type'
    autoload :UndefinedType, 'click_house/type/undefined_type'
    autoload :DateType, 'click_house/type/date_type'
    autoload :DateTimeType, 'click_house/type/date_time_type'
    autoload :DateTime64Type, 'click_house/type/date_time64_type'
    autoload :IntegerType, 'click_house/type/integer_type'
    autoload :FloatType, 'click_house/type/float_type'
    autoload :BooleanType, 'click_house/type/boolean_type'
    autoload :DecimalType, 'click_house/type/decimal_type'
    autoload :FixedStringType, 'click_house/type/fixed_string_type'
    autoload :ArrayType, 'click_house/type/array_type'
    autoload :TupleType, 'click_house/type/tuple_type'
    autoload :MapType, 'click_house/type/map_type'
    autoload :StringType, 'click_house/type/string_type'
    autoload :IPType, 'click_house/type/ip_type'
    autoload :LowCardinalityType, 'click_house/type/low_cardinality_type'
  end
end


================================================
FILE: lib/click_house/util/pretty.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Util
    module Pretty
      SIZE_UNITS = %w[B KiB MiB GiB TiB Pib EiB].freeze

      module_function

      # rubocop:disable all
      def size(bytes)
        return '0B' if bytes == 0

        exp = (Math.log(bytes) / Math.log(1024)).to_i
        exp = 6 if exp > 6

        format('%.1f%s', bytes.to_f / 1024**exp, SIZE_UNITS[exp])
      end
      # rubocop:enable all

      def measure(ms)
        "#{ms.round}MS"
      end

      def squish(string)
        string.gsub(/[[:space:]]+/, ' ').strip
      end
    end
  end
end


================================================
FILE: lib/click_house/util/statement.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Util
    module Statement
      module_function

      def ensure(truthful, value, fallback = nil)
        truthful ? value : fallback
      end
    end
  end
end


================================================
FILE: lib/click_house/util.rb
================================================
# frozen_string_literal: true

module ClickHouse
  module Util
    autoload :Statement, 'click_house/util/statement'
    autoload :Pretty, 'click_house/util/pretty'

    module_function

    # wraps
    def array(input)
      input.is_a?(Array) ? input : [input]
    end
  end
end


================================================
FILE: lib/click_house/version.rb
================================================
# frozen_string_literal: true

module ClickHouse
  VERSION = '2.1.2'
end


================================================
FILE: lib/click_house.rb
================================================
# frozen_string_literal: true

require 'date'
require 'json'
require 'csv'
require 'uri'
require 'logger'
require 'faraday'
require 'forwardable'
require 'bigdecimal'
require 'active_support/core_ext/time/calculations'
require 'click_house/version'
require 'click_house/errors'
require 'click_house/response'
require 'click_house/serializer'
require 'click_house/type'
require 'click_house/middleware'
require 'click_house/extend'
require 'click_house/ast'
require 'click_house/util'
require 'click_house/definition'

module ClickHouse
  extend Extend::TypeDefinition
  extend Extend::Configurable
  extend Extend::Connectible

  autoload :Config, 'click_house/config'
  autoload :Connection, 'click_house/connection'

  add_type 'Array', Type::ArrayType.new
  add_type 'Nullable', Type::NullableType.new
  add_type 'Map', Type::MapType.new
  add_type 'LowCardinality', Type::LowCardinalityType.new
  add_type 'Tuple', Type::TupleType.new

  %w[Bool].each do |column|
    add_type column, Type::BooleanType.new
  end

  %w[Date].each do |column|
    add_type column, Type::DateType.new
  end

  %w[String FixedString(%d) UUID].each do |column|
    add_type column, Type::StringType.new
  end

  %w[DateTime DateTime(%s)].each do |column|
    add_type column, Type::DateTimeType.new
  end

  ['DateTime64(%d)', 'DateTime64(%d, %s)'].each do |column|
    add_type column, Type::DateTime64Type.new
  end

  ['Decimal(%d, %d)', 'Decimal32(%d)', 'Decimal64(%d)', 'Decimal128(%d)', 'Decimal256(%d)'].each do |column|
    add_type column, Type::DecimalType.new
  end

  %w[UInt8 UInt16 UInt32 UInt64 Int8 Int16 Int32 Int64].each do |column|
    add_type column, Type::IntegerType.new
  end

  %w[Float32 Float64].each do |column|
    add_type column, Type::FloatType.new
  end

  %w[IPv4 IPv6].each do |column|
    add_type column, Type::IPType.new
  end
end


================================================
FILE: log/.keep
================================================


================================================
FILE: spec/click_house/ast/parser_spec.rb
================================================
RSpec.describe ClickHouse::Ast::Parser do
  let(:expectations) do
    {
      "Int" => 'Int',
      "DateTime('Asia/Istanbul')" => "DateTime('Asia/Istanbul')",
      "Array(Int, String(2))" => "Array(Int,String(2))",
      "Array(Array(Array(Array(Nullable(Int, String)))))" => "Array(Array(Array(Array(Nullable(Int,String)))))",
      "Function(Decimal(1, 2), Map(Decimal(3, 4), Decimal(5, 6)))" => "Function(Decimal(1,2),Map(Decimal(3,4),Decimal(5,6)))",
      "Array(Map(Decimal(1, 2), Decimal(3, 4)))" => "Array(Map(Decimal(1,2),Decimal(3,4)))",
      "Map(Decimal(1, 2), Decimal(3, 4))" => "Map(Decimal(1,2),Decimal(3,4))",
      "Map(Decimal(1,2))" => "Map(Decimal(1,2))",
      "Map(String, Decimal(1,2))" => "Map(String,Decimal(1,2))",
      "Decimal(1,2)" => "Decimal(1,2)",
      "A(1)" => "A(1)",
      "A(1, 2)" => "A(1,2)",
      "A(B(1))" => "A(B(1))",
      "A(B(1), B(2))" => "A(B(1),B(2))",
      "Enum8('hello' = 1, 'world' = 2)" => "Enum8('hello' = 1,'world' = 2)"
    }
  end

  it 'works' do
    expectations.each do |statement, expect|
      expect(described_class.new(statement).parse.to_s).to eq(expect)
    end
  end

  context 'when Array with nested type' do
    subject do
      described_class.new("Array(String(2))").parse
    end

    it 'works' do
      expect(subject.name).to eq('Array')
      expect(subject.arguments).to have_attributes(size: 1)
      expect(subject.arguments.first.name).to eq("String")
      expect(subject.arguments.first.arguments).to have_attributes(size: 1)
      expect(subject.arguments.first.arguments.first.name).to eq("2")
    end
  end

  context 'when space between arguments' do
    subject do
      described_class.new("Foo(10, 'bar')").parse
    end

    it 'works' do
      expect(subject.name).to eq('Foo')
      expect(subject.arguments.map(&:placeholder)).to eq(%w[%d %s])
      expect(subject.arguments.map(&:value)).to eq([10, 'bar'])
    end
  end
end


================================================
FILE: spec/click_house/config_spec.rb
================================================
RSpec.describe ClickHouse::Config do
  describe '#assign' do
    it 'works' do
      expect { subject.assign(port: 33) }.to change { subject.port }.to(33)
    end

    it 'returns self' do
      expect(subject.assign({})).to be_a(described_class)
    end
  end

  describe '#initialize' do
    context 'when params' do
      it 'works' do
        expect(described_class.new(port: 33).port).to eq(33)
      end
    end

    context 'when block' do
      it 'works' do
        expect(described_class.new { |c| c.port = 33 }.port).to eq(33)
      end
    end
  end

  describe '#auth?' do
    context 'when credentials empty' do
      before do
        subject.username = nil
        subject.password = nil
      end

      it 'is false' do
        expect(subject.auth?).to eq(false)
      end
    end

    context 'when credentials exists' do
      before do
        subject.username = 'foo'
        subject.password = 'bar'
      end

      it 'is true' do
        expect(subject.auth?).to eq(true)
      end
    end
  end

  describe '#url!' do
    before do
      subject.url = 'http://example.com'
      subject.scheme = 'https'
      subject.host = 'clickhouse'
      subject.port = '3344'
    end

    context 'when url exists' do
      it 'works' do
        expect(subject.url!).to eq('http://example.com')
      end
    end

    context 'when url empty' do
      before do
        subject.url = nil
      end

      it 'works' do
        expect(subject.url!).to eq('https://clickhouse:3344')
      end
    end
  end
end


================================================
FILE: spec/click_house/connection_spec.rb
================================================
RSpec.describe ClickHouse::Connection do
  context 'when basic auth' do
    subject do
      ClickHouse::Connection.new(ClickHouse.config.clone.assign(
        username: 'user',
        password: 'password'
      ))
    end

    it 'works' do
      expect {  subject.tables }.to raise_error(ClickHouse::DbException, /Authentication failed/)
    end
  end
end


================================================
FILE: spec/click_house/definition/column_set_spec.rb
================================================
RSpec.describe ClickHouse::Definition::ColumnSet do
  def squish(string)
    string.gsub(/[[:space:]]/, '').strip
  end

  context 'when integration' do
    subject do
      described_class.new do |t|
        t.Decimal :money, 5, 5
        t.UInt16  :year_birth, low_cardinality: true
        t.UInt16  :year_death, low_cardinality: true, nullable: true, default: 0
        t.Float32 :city_id, default: 0, nullable: true
        t.Nested :json do |n|
          n.UInt8 :cid, nullable: true
          n.Date  :created_at, default: 'NOW()'
          n.DateTime :updated_at, 'UTC'
          n.DateTime64 :deleted_at, 6, 'UTC'
        end
        t << "words Enum('hello' = 1, 'world' = 2)"
        t << "tags Array(String)"
      end
    end

    let(:expectation) do
      <<~SQL
        ( 
          money Decimal(5, 5), 
          year_birth LowCardinality(UInt16), 
          year_death LowCardinality(Nullable(UInt16)) DEFAULT 0,
          city_id Nullable(Float32) DEFAULT 0, 
          json Nested ( 
                        cid Nullable(UInt8) , 
                        created_at Date DEFAULT NOW(), 
                        updated_at DateTime('UTC'),
                        deleted_at DateTime64(6, 'UTC')  
                      ), 
          words Enum('hello' = 1, 'world' = 2), 
          tags Array(String) 
        )
      SQL
    end

    it 'works' do
      expect(squish(subject.to_s)).to eq(squish(expectation))
    end
  end
end


================================================
FILE: spec/click_house/extend/connection_altering_spec.rb
================================================
RSpec.describe ClickHouse::Extend::ConnectionAltering do
  subject do
    ClickHouse.connection
  end

  describe '#add_column' do
    before do
      subject.execute <<~SQL
        CREATE TABLE rspec (date Date, id UInt32, user_id UInt32) ENGINE MergeTree() ORDER BY date
      SQL
    end

    context 'when not exists' do
      before do
        subject.add_column(:rspec, :account_id, :UInt64, default: 0, after: :date)
      end

      let(:column) do
        subject.describe_table('rspec').find { |r| r['name'] == 'account_id' }
      end

      it 'works' do
        expect(subject.describe_table('rspec').map { |r| r['name'] }).to eq(%w[date account_id id user_id])
        expect(column).to include('type' => 'UInt64')
        expect(column).to include('default_expression' => '0')
      end
    end

    context 'when exists' do
      let(:function) do
        subject.add_column(:rspec, :user_id, 'UInt32')
      end

      it 'errors' do
        expect { function }.to raise_error(ClickHouse::DbException)
      end
    end

    context 'when if not exists' do
      let(:function) do
        subject.add_column(:rspec, :user_id, 'UInt32', if_not_exists: true)
      end

      it 'works' do
        expect(function).to eq(true)
      end
    end
  end

  describe '#drop_column' do
    before do
      subject.execute <<~SQL
        CREATE TABLE rspec (date Date, id UInt32, int_1 UInt32) ENGINE MergeTree() ORDER BY date
      SQL
    end

    context 'when exists' do
      let(:function) do
        subject.drop_column('rspec', :int_1)
      end

      it 'works' do
        expect { function }.to change { subject.describe_table('rspec').length }.by(-1)
      end
    end

    context 'when not exists' do
      let(:function) do
        subject.drop_column('rspec', :foo)
      end

      it 'errors' do
        expect { function }.to raise_error(ClickHouse::DbException)
      end
    end

    context 'when if exists' do
      let(:function) do
        subject.drop_column('rspec', :foo, if_exists: true)
      end

      it 'works' do
        expect(function).to eq(true)
      end
    end
  end

  describe '#modify_column' do
    before do
      subject.execute <<~SQL
        CREATE TABLE rspec (date Date, id UInt32, int_1 UInt32) ENGINE MergeTree() ORDER BY date
      SQL
    end

    context 'when exists' do
      let(:function) do
        subject.modify_column('rspec', 'int_1', type: :UInt64, default: 0)
      end

      let(:column) do
        -> { subject.describe_table('rspec').find { |r| r['name'] == 'int_1' } }
      end

      it 'works' do
        expect { function }.to change { column.call.values_at('type', 'default_expression') }.to(['UInt64', '0'])
      end
    end

    context 'when not exists' do
      let(:function) do
        subject.modify_column('rspec', 'foo', type: :UInt64)
      end

      it 'errors' do
        expect { function }.to raise_error(ClickHouse::DbException)
      end
    end

    context 'when if exists' do
      let(:function) do
        subject.modify_column('rspec', 'foo', type: :UInt64, if_exists: true)
      end

      it 'works' do
        expect(function).to eq(true)
      end
    end
  end

  describe '#alter_table' do
    before do
      subject.execute <<~SQL
        CREATE TABLE rspec (date Date, id UInt32, int_1 UInt32) ENGINE MergeTree() ORDER BY date
      SQL
    end

    context 'when argument' do
      let(:function) do
        subject.alter_table('rspec', 'DROP COLUMN int_1')
      end

      it 'works' do
        expect { function }.to change { subject.describe_table('rspec').length }.by(-1)
      end
    end

    context 'when block' do
      let(:function) do
        subject.alter_table('rspec') do
          'DROP COLUMN int_1'
        end
      end

      it 'works' do
        expect { function }.to change { subject.describe_table('rspec').length }.by(-1)
      end
    end
  end

  describe '#add_index, #drop_index' do
    before do
      subject.execute <<~SQL
        CREATE TABLE rspec(a String, b Array(String)) engine=MergeTree() order by a;
      SQL
    end

    it 'works' do
      expect(subject.add_index('rspec', 'ix', 'has(b, a)', type: 'minmax', granularity: 2)).to eq(true)
      expect(subject.drop_index('rspec', 'ix')).to eq(true)
    end
  end
end


================================================
FILE: spec/click_house/extend/connection_database_spec.rb
================================================
RSpec.describe ClickHouse::Extend::ConnectionDatabase do
  subject do
    ClickHouse.connection
  end

  describe '#databases' do
    it 'works' do
      expect(subject.databases).to include('default', 'system', ClickHouse.config.database)
    end
  end

  describe '#create_database' do
    context 'when exists' do
      it 'errors' do
        expect { subject.create_database(ClickHouse.config.database) }.to raise_error(ClickHouse::DbException)
      end
    end

    context 'when if not exists' do
      it 'works' do
        expect(subject.create_database(ClickHouse.config.database, if_not_exists: true)).to eq(true)
      end
    end

    context 'when default' do
      it 'works' do
        expect(subject.create_database('foo')).to eq(true)
      end
    end

    context 'when engine' do
      it 'works' do
        expect(subject.create_database('foo', engine: 'Lazy(10)')).to eq(true)
      end
    end
  end

  describe '#drop_database' do
    context 'when not exists' do
      it 'errors' do
        expect { subject.drop_database('foo') }.to raise_error(ClickHouse::DbException)
      end
    end

    context 'when exists' do
      it 'works' do
        expect(subject.drop_database('foo', if_exists: true)).to eq(true)
      end
    end
  end
end


================================================
FILE: spec/click_house/extend/connection_explaining_spec.rb
================================================
RSpec.describe ClickHouse::Extend::ConnectionExplaining do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(id Int64) ENGINE TinyLog
    SQL
  end

  let(:expectation) do
    <<~TXT
      Expression ((Projection + Before ORDER BY))
        Join (JOIN FillRightFirst)
          Expression (Before JOIN)
            ReadFromStorage (TinyLog)
          Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY))))
            ReadFromStorage (TinyLog)
    TXT
  end

  context 'when normal query' do
    it 'works' do
      buffer = StringIO.new
      subject.explain('SELECT 1 FROM rspec CROSS JOIN rspec', io: buffer)
      expect(buffer.string).to eq(expectation)
    end
  end

  context 'when EXPLAIN query' do
    it 'works' do
      buffer = StringIO.new
      subject.explain('EXPLAIN SELECT 1 FROM rspec CROSS JOIN rspec', io: buffer)
      expect(buffer.string).to eq(expectation)
    end
  end
end


================================================
FILE: spec/click_house/extend/connection_healthy_spec.rb
================================================
RSpec.describe ClickHouse::Extend::ConnectionHealthy do
  subject do
    ClickHouse::Connection.new(ClickHouse.config)
  end

  describe '#ping' do
    context 'when ok' do
      it 'works' do
        expect(subject.ping).to eq(true)
      end
    end

    context 'when fail' do
      before do
        subject.transport.port = '80'
      end

      it 'errors' do
        expect { subject.ping }.to raise_error(ClickHouse::NetworkException)
      end
    end
  end

  describe '#replicas_status' do
    context 'when ok' do
      it 'works' do
        expect(subject.replicas_status).to eq(true)
      end
    end

    context 'when fail' do
      before do
        subject.transport.port = '80'
      end

      it 'errors' do
        expect { subject.replicas_status }.to raise_error(ClickHouse::NetworkException)
      end
    end
  end
end


================================================
FILE: spec/click_house/extend/connection_inserting_spec.rb
================================================
# ⚠️ INSERT IN TESTS SHOULD HAVE A DIFFERENT ORDER OF COLUMNS
#   FROM THE ORDER IN THE TABLE ITSELF
RSpec.describe ClickHouse::Extend::ConnectionInserting do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(id Int64, name Nullable(String)) ENGINE Memory
    SQL
  end

  def expected(insert, count)
    expect(insert.written_rows).to eq(count)
    expect(subject.select_value('SELECT COUNT(*) FROM rspec')).to eq(count)
  end

  context 'when blank' do
    let(:insert) do
      subject.insert('rspec')
    end

    it 'works' do
      expected(insert, 0)
    end
  end

  context 'when columns with blank values' do
    let(:insert) do
      subject.insert('rspec', columns: %i[id name])
    end

    it 'works' do
      expected(insert, 0)
    end
  end

  describe 'execution' do
    let(:insert) do
      subject.insert('rspec', values: {id: 1, name: 'foo'})
    end

    it 'has proper attributes' do
      expect(insert.read_rows).to be > 0
      expect(insert.read_bytes).to be > 0
      expect(insert.written_rows).to be > 0
      expect(insert.written_bytes).to be > 0
      expect(insert.result_rows).to be > 0
      expect(insert.result_bytes).to be > 0
      expect(insert.summary).not_to be_empty
      expect(insert.headers).not_to be_empty
    end
  end

  context 'when body', if: ruby_version_gt('3') do
    context 'when Hash' do
      let(:insert) do
        subject.insert('rspec', {id: 1, name: 'foo'})
      end

      it 'works' do
        expected(insert, 1)
      end
    end

    context 'when Array' do
      let(:insert) do
        subject.insert('rspec', [{id: 1, name: 'foo'}, {id: 1, name: 'foo'}])
      end

      it 'works' do
        expected(insert, 2)
      end
    end
  end

  context 'when body', if: ruby_version_lt('3') do
    context 'when Hash' do
      let(:insert) do
        subject.insert('rspec', {id: 1, name: 'foo'}, {})
      end

      it 'works' do
        expected(insert, 1)
      end
    end

    context 'when Array' do
      let(:insert) do
        subject.insert('rspec', [{id: 1, name: 'foo'}, {id: 1, name: 'foo'}], {})
      end

      it 'works' do
        expected(insert, 2)
      end
    end
  end

  context 'when block with columns' do
    let(:insert) do
      subject.insert('rspec', columns: %i[name id], values: [['Sun', 1], ['Moon', 2]])
    end

    it 'works' do
      expected(insert, 2)
    end

    context 'when string format' do
      let(:insert) do
        subject.insert('rspec', columns: %i[name id], values: [%w[Sun 1], %w[Moon 2]], format: 'JSONCompactStringsEachRow')
      end

      it 'works' do
        expected(insert, 2)
      end
    end
  end

  context 'when argument with columns' do
    let(:insert) do
      subject.insert('rspec', columns: %i[name id]) do |buffer|
        buffer << ['Sun', 1]
        buffer << ['Moon', 2]
      end
    end

    it 'works' do
      expected(insert, 2)
    end
  end

  context 'when hash with argument' do
    let(:insert) do
      subject.insert('rspec', values: [{ name: 'Sun', id: 1 }, { name: 'Moon', id: 2 }])
    end

    it 'works' do
      expected(insert, 2)
    end
  end

  context 'when hash with block' do
    let(:insert) do
      subject.insert('rspec') do |buffer|
        buffer << { name: 'Sun', id: 1 }
        buffer << { name: 'Moon', id: 2 }
      end
    end

    it 'works' do
      expected(insert, 2)
    end
  end
end


================================================
FILE: spec/click_house/extend/connection_selective_spec.rb
================================================
RSpec.describe ClickHouse::Extend::ConnectionSelective do
  subject do
    ClickHouse.connection
  end

  describe '#select_value' do
    context 'when exists' do
      it 'works' do
        expect(subject.select_value('SELECT 13')).to eq(13)
      end
    end

    context 'when not exists' do
      it 'works' do
        expect(subject.select_value('SELECT null')).to eq(nil)
      end
    end

    context 'when multiple columns' do
      it 'works' do
        expect(subject.select_value('SELECT 1, 2, 3, 4, 5')).to eq(1)
      end
    end
  end

  describe '#select_one' do
    context 'when exists' do
      it 'works' do
        expect(subject.select_one('SELECT 1 AS foo, 2 AS bar')).to eq({ 'foo' => 1, 'bar' => 2 })
      end
    end

    context 'when not exists' do
      it 'works' do
        expect(subject.select_one('SELECT NULL')).to eq({ 'NULL' => nil })
      end
    end
  end

  describe '#select_all' do
    before do
      subject.execute <<~SQL
        CREATE TABLE rspec (date Date, id UInt32) ENGINE TinyLog
      SQL

      subject.execute <<~SQL
        INSERT INTO rspec (date, id) VALUES('2000-01-01', 1), ('2000-01-02', 2)
      SQL
    end

    context 'when empty' do
      it 'works' do
        expect(subject.select_all('SELECT * FROM rspec where id = 100').to_a).to eq([])
      end
    end

    context 'when exists' do
      let(:expectation) do
        [
          { 'date' => Date.new(2000, 1, 1), 'id' => 1 },
          { 'date' => Date.new(2000, 1, 2), 'id' => 2 }
        ]
      end

      it 'works' do
        expect(subject.select_all('SELECT * FROM rspec').to_a).to match_array(expectation)
      end
    end
  end
end


================================================
FILE: spec/click_house/extend/connection_table_spec.rb
================================================
RSpec.describe ClickHouse::Extend::ConnectionTable do
  subject do
    ClickHouse.connection
  end

  describe '#tables' do
    context 'when empty' do
      it 'works' do
        expect(subject.tables).to eq([])
      end
    end

    context 'when exists' do
      before do
        subject.execute <<~SQL
          CREATE TABLE rspec (date Date, id UInt32) ENGINE TinyLog
        SQL
      end

      it 'works' do
        expect(subject.tables).to contain_exactly('rspec')
      end
    end
  end

  describe '#table_exists?' do
    context 'when not exists' do
      it 'works' do
        expect(subject.table_exists?('foo')).to eq(false)
      end
    end

    context 'when exists' do
      before do
        subject.execute <<~SQL
          CREATE TABLE rspec (date Date, id UInt32) ENGINE TinyLog
        SQL
      end

      it 'works' do
        expect(subject.table_exists?('rspec')).to eq(true)
      end
    end
  end

  describe '#describe_table' do
    context 'when nested' do
      before do
        subject.execute <<~SQL
          CREATE TABLE rspec (
            date Date,
            id UInt32,
            json Nested (uid UInt32)
          ) ENGINE TinyLog
        SQL
      end

      let(:expectation) do
        [
          {'name' =>'date', 'type' =>'Date', 'default_type' =>'', 'default_expression' =>'', 'comment' =>'', 'codec_expression' =>'', 'ttl_expression' =>''},
          {'name' =>'id', 'type' =>'UInt32', 'default_type' =>'', 'default_expression' =>'', 'comment' =>'', 'codec_expression' =>'', 'ttl_expression' =>''},
          {'name' =>'json.uid', 'type' =>'Array(UInt32)', 'default_type' =>'', 'default_expression' =>'', 'comment' =>'', 'codec_expression' =>'', 'ttl_expression' =>''}
        ]
      end

      it 'works' do
        expect(subject.describe_table('rspec').to_a).to eq(expectation)
      end
    end

    context 'when table not exists' do
      it 'errors' do
        expect { subject.describe_table('foo') }.to raise_error(ClickHouse::DbException)
      end
    end
  end

  describe '#drop_table' do
    context 'when not exists' do
      it 'errors' do
        expect { subject.drop_table('foo') }.to raise_error(ClickHouse::DbException)
      end
    end

    context 'when if exists' do
      it 'works' do
        expect(subject.drop_table('foo', if_exists: true)).to eq(true)
      end
    end

    context 'when default' do
      before do
        subject.execute <<~SQL
          CREATE TABLE rspec (date Date, id UInt32) ENGINE TinyLog
        SQL
      end

      it 'works' do
        expect(subject.drop_table('rspec')).to eq(true)
      end
    end
  end

  describe '#truncate_table' do
    context 'when table exists' do
      before do
        subject.execute <<~SQL
          CREATE TABLE rspec(id Int64) ENGINE TinyLog
        SQL

        subject.insert('rspec', columns: %i[id], values: [[1]])
      end

      it 'works' do
        expect { subject.truncate_table('rspec') }.to change { subject.select_value('SELECT COUNT(*) from rspec') }.from(1).to(0)
      end
    end

    context 'when table not exists' do
      it 'errors' do
        expect { subject.truncate_table('rspec') }.to raise_error(ClickHouse::DbException)
      end
    end

    context 'when if exists' do
      it 'works' do
        expect(subject.truncate_table('rspec', if_exists: true)).to eq(true)
      end
    end
  end

  describe '#truncate_tables' do
    before do
      subject.execute <<~SQL
        CREATE TABLE rspec_1(id Int64) ENGINE TinyLog
      SQL

      subject.execute <<~SQL
        CREATE TABLE rspec_2(id Int64) ENGINE TinyLog
      SQL

      subject.insert('rspec_1', columns: %i[id], values: [[1]])
      subject.insert('rspec_2', columns: %i[id], values: [[1]])
    end

    it 'works' do
      sql = <<~SQL
        SELECT (SELECT COUNT(*) FROM rspec_1) + (SELECT COUNT(*) FROM rspec_2)
      SQL

      expect { subject.truncate_tables }.to change { subject.select_value(sql) }.from(2).to(0)
    end
  end

  describe '#rename_table' do
    before do
      subject.execute <<~SQL
        CREATE TABLE bar(id Int64) ENGINE TinyLog
      SQL

      subject.execute <<~SQL
        CREATE TABLE foo(id Int64) ENGINE TinyLog
      SQL
    end

    context 'when 1 to 1' do
      it 'works' do
        expect { subject.rename_table('bar', 'baz') }.to change { subject.tables }.from(%w[bar foo]).to(%w[baz foo])
      end
    end

    context 'when many to many' do
      it 'works' do
        expect { subject.rename_table(%w[bar foo], %w[baz foz]) }.to change { subject.tables }.from(%w[bar foo]).to(%w[baz foz])
      end
    end

    context 'when incorrect arity' do
      it 'errors' do
        expect { subject.rename_table(%w[bar foo], %w[baz]) }.to raise_error(ClickHouse::StatementException)
      end
    end
  end

  describe '#create_table' do
    context 'when column options' do
      before do
        subject.create_table('rspec', engine: 'MergeTree()', order: 'date') do |t|
          t.UInt16      :id, 16, default: 0, ttl: 'date + INTERVAL 1 DAY'
          t.UInt16      :year
          t.IPv4        :ipv4
          t.IPv6        :ipv6
          t.Date        :date
          t.DateTime    :time, 'UTC'
          t.DateTime64  :time_with_usec, 4, 'UTC'
          t.Decimal     :money, 5, 4
          t.String      :event, nullable: true
          t.Nested      :json do |n|
            n.UInt8     :cid
            n.Date      :created_at
          end
          t << "vendor Enum('microsoft' = 1, 'apple' = 2)"
        end
      end

      let(:columns) do
        subject.describe_table('rspec').each_with_object({}) do |column, object|
          object[column.fetch('name')] = column
        end
      end

      it 'works' do
        expect(columns.fetch('id')).to include('type' => 'UInt16', 'default_expression' => '0', 'ttl_expression' => 'date + toIntervalDay(1)')
        expect(columns.fetch('year')).to include('type' => 'UInt16', 'default_expression' => '', 'ttl_expression' => '')
        expect(columns.fetch('ipv4')).to include('type' => 'IPv4', 'default_expression' => '', 'ttl_expression' => '')
        expect(columns.fetch('ipv6')).to include('type' => 'IPv6', 'default_expression' => '', 'ttl_expression' => '')
        expect(columns.fetch('date')).to include('type' => 'Date', 'default_expression' => '', 'ttl_expression' => '')
        expect(columns.fetch('time')).to include('type' => "DateTime('UTC')", 'default_expression' => '', 'ttl_expression' => '')
        expect(columns.fetch('time_with_usec')).to include('type' => "DateTime64(4, 'UTC')", 'default_expression' => '', 'ttl_expression' => '')
        expect(columns.fetch('money')).to include('type' => 'Decimal(5, 4)', 'default_expression' => '', 'ttl_expression' => '')
        expect(columns.fetch('event')).to include('type' => 'Nullable(String)', 'default_expression' => '', 'ttl_expression' => '')
        expect(columns.fetch('json.cid')).to include('type' => 'Array(UInt8)', 'default_expression' => '', 'ttl_expression' => '')
        expect(columns.fetch('json.created_at')).to include('type' => 'Array(Date)', 'default_expression' => '', 'ttl_expression' => '')
        expect(columns.fetch('vendor')).to include('type' => "Enum8('microsoft' = 1, 'apple' = 2)")
      end
    end

    context 'when table options' do
      before do
        subject.create_table('rspec',
          order: 'year',
          ttl: 'date + INTERVAL 1 DAY',
          sample: 'year',
          settings: 'index_granularity=8192',
          primary_key: 'year',
          engine: 'MergeTree()') do |t|
          t.UInt16  :year
          t.Date    :date
        end
      end

      let(:schema) do
        subject.execute('SHOW CREATE rspec FORMAT TabSeparatedRaw').body
      end

      let(:expectation) do
        <<~SQL
          CREATE TABLE click_house_rspec.rspec 
          (
              `year` UInt16, 
              `date` Date
          ) 
          ENGINE = MergeTree 
          PRIMARY KEY year 
          ORDER BY year 
          SAMPLE BY year
          TTL date + toIntervalDay(1) 
          SETTINGS index_granularity = 8192
        SQL
      end

      it 'works' do
        expect(ClickHouse::Util::Pretty.squish(schema)).to eq(ClickHouse::Util::Pretty.squish(expectation))
      end
    end
  end
end


================================================
FILE: spec/click_house/integration/array_spec.rb
================================================
RSpec.describe ClickHouse::Type::ArrayType do
  subject do
    ClickHouse.connection
  end

  describe 'cast' do
    context 'many flat' do
      before do
        subject.execute <<~SQL
          CREATE TABLE rspec(
              a Array(DateTime),
              b Array(Nullable(DateTime)),
              c Array(DateTime64(3)),
              d Array(Nullable(DateTime64(3))),
              e Array(DateTime64(3, 'UTC')),
              f Array(Nullable(DateTime64(3, 'UTC'))),
              g Array(Decimal(10,2)),
              h Array(String)
           ) ENGINE Memory
        SQL

        subject.execute <<~SQL
          insert into rspec values (
            array(now()),
            array(now()),
            array(now()),
            array(now()),
            array(now()),
            array(now()),
            array(5.99),
            array('foo')
          );
        SQL
      end

      it 'works' do
        got = subject.select_one('SELECT * FROM rspec')
        expect(got.fetch('a').first).to be_a(Time)
        expect(got.fetch('b').first).to be_a(Time)
        expect(got.fetch('c').first).to be_a(Time)
        expect(got.fetch('d').first).to be_a(Time)
        expect(got.fetch('e').first).to be_a(Time)
        expect(got.fetch('f').first).to be_a(Time)
        expect(got.fetch('g').first).to be_a(BigDecimal)
        expect(got.fetch('h')).to eq(['foo'])
      end
    end

    context 'many nested' do
      before do
        subject.execute <<~SQL
          CREATE TABLE rspec(
              a Array(Array(DateTime)),
              b Array(Array(Nullable(DateTime))),
              c Array(Array(Array(DateTime64(3)))),
              d Array(Array(Array(Array(Nullable(DateTime64(3)))))),
              e Array(Array(Array(Array(Array(DateTime64(3, 'UTC')))))),
              f Array(Array(Array(Array(Array(Array(Nullable(DateTime64(3, 'UTC')))))))),
              g Array(Array(Array(Array(Array(Array(Array(Decimal(10,2))))))))
           ) ENGINE TinyLog
        SQL

        subject.execute <<~SQL
          insert into rspec values (
            array(array(now())),
            array(array((now()))),
            array(array(array(((now()))))),
            array(array(array(array((((now()))))))),
            array(array(array(array(array((((now())))))))),
            array(array(array(array(array(array((((now()))))))))),
            array(array(array(array(array(array(array((((5.99))))))))))
          );
        SQL
      end

      it 'works' do
        got = subject.select_one('SELECT * FROM rspec')
        expect(got.fetch('a').dig(0, 0)).to be_a(Time)
        expect(got.fetch('b').dig(0, 0)).to be_a(Time)
        expect(got.fetch('c').dig(0, 0, 0)).to be_a(Time)
        expect(got.fetch('d').dig(0, 0, 0, 0)).to be_a(Time)
        expect(got.fetch('e').dig(0, 0, 0, 0, 0)).to be_a(Time)
        expect(got.fetch('f').dig(0, 0, 0, 0, 0, 0)).to be_a(Time)
        expect(got.fetch('g').dig(0, 0, 0, 0, 0, 0, 0)).to be_a(BigDecimal)
      end
    end
  end

  describe 'serialize' do
    before do
      subject.execute <<~SQL
          CREATE TABLE rspec(
              a Array(Bool),
              b Array(DateTime64(9, 'Europe/Kyiv')),
              c Array(Array(UInt8)),
              d Array(Array(Array(Nullable(UInt8)))),
              e Array(String),          
           ) ENGINE Memory
      SQL
    end

    let(:row) do
      {
        'a' => [true],
        'b' => [Time.find_zone("Europe/Kyiv").now],
        'c' => [[1]],
        'd' => [[[nil]]],
        'e' => ['foo']
      }
    end

    it 'works' do
      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))
      got = subject.select_one('SELECT * FROM rspec')

      expect(got.fetch('a')).to eq(row.fetch('a'))
      expect(got.fetch('b')).to eq(row.fetch('b'))
      expect(got.fetch('c')).to eq(row.fetch('c'))
      expect(got.fetch('d')).to eq(row.fetch('d'))
      expect(got.fetch('e')).to eq(row.fetch('e'))
    end
  end
end


================================================
FILE: spec/click_house/integration/boolean_type_spec.rb
================================================
# frozen_string_literal: true

RSpec.describe ClickHouse::Type::BooleanType do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
          a Boolean,
          b Boolean,
          c Boolean,
          d Boolean,
          e Nullable(Boolean)
       ) ENGINE Memory
    SQL
  end

  describe 'cast' do
    before do
      subject.execute <<~SQL
        INSERT INTO rspec VALUES (
          1,
          0,
          true,
          false,
          NULL
        );
      SQL
    end

    it 'works' do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got.fetch('a')).to be_a(TrueClass)
      expect(got.fetch('b')).to be_a(FalseClass)
      expect(got.fetch('c')).to be_a(TrueClass)
      expect(got.fetch('b')).to be_a(FalseClass)
      expect(got.fetch('e')).to be_a(NilClass)
    end
  end

  describe 'serialize' do
    let(:row) do
      {
        'a' => true,
        'b' => false,
        'c' => 1,
        'd' => 0,
        'e' => nil
      }
    end

    it 'works' do
      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))
      got = subject.select_one('SELECT * FROM rspec')

      expect(got.fetch('a')).to be_a(TrueClass)
      expect(got.fetch('b')).to be_a(FalseClass)
      expect(got.fetch('c')).to be_a(TrueClass)
      expect(got.fetch('b')).to be_a(FalseClass)
      expect(got.fetch('e')).to be_a(NilClass)
    end
  end
end


================================================
FILE: spec/click_house/integration/date_spec.rb
================================================
# frozen_string_literal: true

RSpec.describe ClickHouse::Type::IPType do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
          a Date,
          b Nullable(Date)
       ) ENGINE Memory
    SQL
  end

  describe 'cast' do
    before do
      subject.execute <<~SQL
        INSERT INTO rspec VALUES (
          '2022-01-02',
          NULL
        );
      SQL
    end

    it 'works' do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got.fetch('a')).to eq(Date.new(2022, 1, 2))
      expect(got.fetch('b')).to eq(nil)
    end
  end

  describe 'serialize' do
    let(:row) do
      {
        'a' => Date.new(2022, 1, 2),
        'b' => nil
      }
    end

    it 'works' do
      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))
      got = subject.select_one('SELECT * FROM rspec')

      expect(got.fetch('a')).to eq(row.fetch('a'))
      expect(got.fetch('b')).to eq(row.fetch('b'))
    end
  end
end


================================================
FILE: spec/click_house/integration/date_time64_spec.rb
================================================
RSpec.describe ClickHouse::Type::DateTime64Type do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
          a DateTime64(0),
          b DateTime64(9, 'Europe/Kyiv'),
          c Nullable(DateTime64(9)),
          d Nullable(DateTime64(9, 'Europe/Kyiv')),
          e Nullable(DateTime64(9)),
       ) ENGINE Memory
    SQL
  end

  describe 'cast' do
    before do
      subject.execute <<~SQL
        INSERT INTO rspec values (
          now(),
          now(),
          now(),
          now(),
          null
        );
      SQL
    end

    it 'works' do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got.fetch('a')).to be_a(Time)
      expect(got.fetch('a')).to have_attributes(zone: Time.now.zone)

      expect(got.fetch('b')).to be_a(Time)
      expect(got.fetch('b')).to have_attributes(zone: Time.find_zone('Europe/Kyiv').tzinfo.abbr)

      expect(got.fetch('c')).to be_a(Time)
      expect(got.fetch('d')).to be_a(Time)
      expect(got.fetch('d')).to have_attributes(zone: Time.find_zone('Europe/Kyiv').tzinfo.abbr)

      expect(got.fetch('e')).to be_a(NilClass)
    end
  end

  describe 'serialize' do
    let(:row) do
      {
        'a' => Time.now.round,
        'b' => Time.find_zone("Europe/Kyiv").now,
        'c' => Time.now,
        'd' => nil,
        'e' => nil,
      }
    end

    it 'works' do
      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))
      got = subject.select_one('SELECT * FROM rspec')

      expect(got.fetch('a')).to eq(row.fetch('a'))
      expect(got.fetch('b')).to eq(row.fetch('b'))
      expect(got.fetch('c')).to eq(row.fetch('c'))
      expect(got.fetch('d')).to eq(row.fetch('d'))
      expect(got.fetch('e')).to eq(row.fetch('e'))
    end
  end
end


================================================
FILE: spec/click_house/integration/date_time_spec.rb
================================================
RSpec.describe ClickHouse::Type::DateTimeType do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
          a DateTime,
          b DateTime('Europe/Kyiv'),
          c Nullable(DateTime),
          d Nullable(DateTime('Europe/Kyiv')),
          e Nullable(DateTime),
       ) ENGINE Memory
    SQL
  end

  describe 'cast' do
    before do
      subject.execute <<~SQL
        INSERT INTO rspec VALUES (
          now(),
          now(),
          now(),
          now(),
          null
        );
      SQL
    end

    it 'works' do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got.fetch('a')).to be_a(Time)
      expect(got.fetch('a')).to have_attributes(zone: Time.now.zone)

      expect(got.fetch('b')).to be_a(Time)
      expect(got.fetch('b')).to have_attributes(zone: Time.find_zone('Europe/Kyiv').tzinfo.abbr)

      expect(got.fetch('c')).to be_a(Time)
      expect(got.fetch('d')).to be_a(Time)
      expect(got.fetch('d')).to have_attributes(zone: Time.find_zone('Europe/Kyiv').tzinfo.abbr)

      expect(got.fetch('e')).to be_a(NilClass)
    end
  end

  describe 'serialize' do
    let(:row) do
      {
        'a' => Time.now.round,
        'b' => Time.find_zone("Europe/Kyiv").now.round,
        'c' => Time.now.round,
        'd' => nil,
        'e' => nil,
      }
    end

    it 'works' do
      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))
      got = subject.select_one('SELECT * FROM rspec')

      expect(got.fetch('a')).to eq(row.fetch('a'))
      expect(got.fetch('b')).to eq(row.fetch('b'))
      expect(got.fetch('c')).to eq(row.fetch('c'))
      expect(got.fetch('d')).to eq(nil)
      expect(got.fetch('e')).to eq(nil)
    end
  end
end


================================================
FILE: spec/click_house/integration/decimal_spec.rb
================================================
RSpec.describe ClickHouse::Type::DecimalType do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
          a Decimal(10,10),
          b Decimal32(1),
          c Decimal64(10),
          d Decimal128(20),
          e Nullable(Decimal256(30))
       ) ENGINE Memory
    SQL
  end

  describe 'cast' do
    before do
      subject.execute <<~SQL
        INSERT INTO rspec VALUES (
          0.1,
          1/3,
          1/3,
          1/3,
          1/3
        );
      SQL
    end

    it 'works' do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got.fetch('a')).to be_a(BigDecimal)
      expect(got.fetch('b')).to be_a(BigDecimal)
      expect(got.fetch('c')).to be_a(BigDecimal)
      expect(got.fetch('d')).to be_a(BigDecimal)
      expect(got.fetch('e')).to be_a(BigDecimal)
    end

    it 'works with correct precision', if: ruby_version_gt('2.8') do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got.fetch('a').precision).to eq(1)
      expect(got.fetch('b').precision).to eq(1)
      expect(got.fetch('c').precision).to eq(10)
      expect(got.fetch('d').precision).to eq(20)
      expect(got.fetch('e').precision).to eq(30)
    end
  end

  describe 'serialize' do
    let(:row) do
      {
        'a' => "0.1",
        'b' => BigDecimal(1),
        'c' => 1.fdiv(3),
        'd' => 1,
        'e' => nil
      }
    end

    it 'works' do
      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))
      got = subject.select_one('SELECT * FROM rspec')

      expect(got.fetch('a')).to eq(BigDecimal("0.1"))
      expect(got.fetch('b')).to eq(BigDecimal(1))
      expect(got.fetch('c')).to eq(BigDecimal(1.fdiv(3), 10))
      expect(got.fetch('d')).to eq(BigDecimal(1))
      expect(got.fetch('e')).to eq(nil)
    end
  end
end


================================================
FILE: spec/click_house/integration/enum_spec.rb
================================================
# frozen_string_literal: true

RSpec.describe 'Enum' do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
         a Enum('foo' = 1, 'bar' = 2),
         b Nullable(Enum('foo' = 1, 'bar' = 2))
       ) ENGINE Memory
    SQL
  end

  describe 'cast' do
    before do
      subject.execute <<~SQL
        INSERT INTO rspec VALUES (
          1, 
          NULL
        )
      SQL
    end

    it 'works' do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got.fetch('a')).to eq('foo')
      expect(got.fetch('b')).to eq(nil)
    end
  end

  describe 'serialize' do
    let(:row) do
      {
        'a' => 'foo',
        'b' => nil,
      }
    end

    it 'works' do
      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))
      got = subject.select_one('SELECT * FROM rspec')

      expect(got.fetch('a')).to eq('foo')
      expect(got.fetch('b')).to eq(nil)
    end
  end
end


================================================
FILE: spec/click_house/integration/float_spec.rb
================================================
# frozen_string_literal: true

RSpec.describe ClickHouse::Type::FloatType do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
          a Float32,
          b Float64,        
          c Nullable(Float64)
       ) ENGINE Memory
    SQL
  end

  describe 'cast' do
    before do
      subject.execute <<~SQL
        INSERT INTO rspec VALUES (
            1.1,
            2.2,
            NULL
        );
      SQL
    end

    it 'works' do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got.fetch('a')).to eq(1.1)
      expect(got.fetch('b')).to eq(2.2)
      expect(got.fetch('c')).to eq(nil)
    end
  end

  describe 'serialize' do
    let(:row) do
      {
        'a' => 1.1,
        'b' => 2.2,
        'c' => nil,
      }
    end

    it 'works' do
      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))
      got = subject.select_one('SELECT * FROM rspec')

      expect(got.fetch('a')).to eq(row.fetch('a'))
      expect(got.fetch('b')).to eq(row.fetch('b'))
      expect(got.fetch('c')).to eq(row.fetch('c'))
    end
  end
end


================================================
FILE: spec/click_house/integration/formats.rb
================================================
# frozen_string_literal: true

RSpec.describe ClickHouse::Extend::ConnectionSelective do
  subject do
    ClickHouse.connection
  end

  context 'when RowBinary' do
    let(:query) do
      'SELECT 1 FORMAT RowBinary'
    end

    it '#select_one' do
      got = subject.select_one(query)
      expect(got).to eq("\u0001")
    end

    it '#select_value' do
      got = subject.select_value(query)
      expect(got).to eq("\u0001")
    end

    it '#summary' do
      got = subject.select_all(query)
      expect(got.summary.read_rows).to eq(1)
    end

    it '#types' do
      got = subject.select_all(query)
      expect(got.types).to eq([])
    end
  end

  context 'when CSV' do
    let(:query) do
      'SELECT 1 FORMAT CSV'
    end

    it '#select_one' do
      got = subject.select_one(query)
      expect(got).to eq(['1'])
    end

    it '#select_value' do
      got = subject.select_value(query)
      expect(got).to eq('1')
    end

    it '#summary' do
      got = subject.select_all(query)
      expect(got.summary.read_rows).to eq(1)
    end

    it '#types' do
      got = subject.select_all(query)
      expect(got.types).to eq([])
    end
  end
end


================================================
FILE: spec/click_house/integration/function_spec.rb
================================================
RSpec.describe 'Functions' do
  subject do
    ClickHouse.connection
  end

  let(:expectations) do 
    {
      'select NOW()' => Time,
      'select 1 + 1' => Integer,
      'select 1 * 1.0' => Float,
      'select empty([])' => Integer,
      'select 1 > 0' => Integer,
    }
  end

  it 'works' do
    expectations.each do |query, klass|
      expect(subject.select_value(query)).to be_a(klass)
    end  
  end
end


================================================
FILE: spec/click_house/integration/integer_spec.rb
================================================
# frozen_string_literal: true

RSpec.describe ClickHouse::Type::IntegerType do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
          a UInt8,
          b UInt16,
          c UInt32, 
          d UInt64,
          e Int8,
          f Int16,
          g Int32,
          h Int64,
          k Nullable(Int64)
       ) ENGINE Memory
    SQL
  end

  describe 'cast' do
    before do
      subject.execute <<~SQL
        INSERT INTO rspec VALUES (
            1,
            2,
            3,
            4,
            5,
            6,
            7,
            8,
            NULL
        );
      SQL
    end

    it 'works' do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got.fetch('a')).to eq(1)
      expect(got.fetch('b')).to eq(2)
      expect(got.fetch('c')).to eq(3)
      expect(got.fetch('d')).to eq(4)
      expect(got.fetch('e')).to eq(5)
      expect(got.fetch('f')).to eq(6)
      expect(got.fetch('g')).to eq(7)
      expect(got.fetch('h')).to eq(8)
      expect(got.fetch('k')).to eq(nil)
    end
  end

  describe 'serialize' do
    let(:row) do
      {
        'a' => 1,
        'b' => 2,
        'c' => 3,
        'd' => 4,
        'e' => 5,
        'f' => 6,
        'g' => 7,
        'h' => 8,
        'k' => nil,
      }
    end

    it 'works' do
      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))
      got = subject.select_one('SELECT * FROM rspec')

      expect(got.fetch('a')).to eq(row.fetch('a'))
      expect(got.fetch('b')).to eq(row.fetch('b'))
      expect(got.fetch('c')).to eq(row.fetch('c'))
      expect(got.fetch('d')).to eq(row.fetch('d'))
      expect(got.fetch('e')).to eq(row.fetch('e'))
      expect(got.fetch('f')).to eq(row.fetch('f'))
      expect(got.fetch('g')).to eq(row.fetch('g'))
      expect(got.fetch('h')).to eq(row.fetch('h'))
      expect(got.fetch('k')).to eq(row.fetch('k'))
    end
  end
end


================================================
FILE: spec/click_house/integration/ip_spec.rb
================================================
# frozen_string_literal: true

RSpec.describe ClickHouse::Type::IPType do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
          a IPv4,
          b Nullable(IPv4),
          c IPv6,
          d Nullable(IPv6)
       ) ENGINE Memory
    SQL
  end

  describe 'cast' do
    before do
      subject.execute <<~SQL
        INSERT INTO rspec VALUES (
          '127.0.0.1',
          '127.0.0.1',
          '::1',
          NULL
        );
      SQL
    end

    it 'works' do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got.fetch('a')).to eq(IPAddr.new('127.0.0.1'))
      expect(got.fetch('b')).to eq(IPAddr.new('127.0.0.1'))
      expect(got.fetch('c')).to eq(IPAddr.new('::1'))
      expect(got.fetch('d')).to eq(nil)
    end
  end

  describe 'serialize' do
    let(:row) do
      {
        'a' => IPAddr.new('127.0.0.1'),
        'b' => '127.0.0.1', # as string
        'c' => IPAddr.new('::1'),
        'd' => nil
      }
    end

    it 'works' do
      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))
      got = subject.select_one('SELECT * FROM rspec')

      expect(got.fetch('a')).to eq(row.fetch('a'))
      expect(got.fetch('b')).to eq(IPAddr.new(row.fetch('b')))
      expect(got.fetch('c')).to eq(row.fetch('c'))
      expect(got.fetch('d')).to eq(row.fetch('d'))
    end
  end
end


================================================
FILE: spec/click_house/integration/loggin_spec.rb
================================================
RSpec.describe ClickHouse::Middleware::Logging do
  subject do
    ClickHouse::Connection.new(ClickHouse.config.clone.assign(logger: logger))
  end

  let(:out) do
    StringIO.new
  end

  let(:logger) do
    Logger.new(out)
  end

  context 'when POST' do
    it 'works' do
      subject.execute('SELECT 1')
      expect(out.string).to match(/Total: \d/)
      expect(out.string).to include('SELECT 1;')
      expect(out.string).to include('Read: 1 rows')
      expect(out.string).to include('Written: 0 rows')
    end
  end

  context 'when GET' do
    it 'works' do
      subject.select_all('SELECT 1')
      expect(out.string).to match(/Total: \d/)
      expect(out.string).to include('SELECT 1;')
      expect(out.string).to include('Read: 1 rows')
      expect(out.string).to include('Written: 0 rows')
    end
  end
end


================================================
FILE: spec/click_house/integration/low_cardinality_spec.rb
================================================
RSpec.describe ClickHouse::Type::LowCardinalityType do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
          a LowCardinality(DateTime),
          b LowCardinality(DateTime('Europe/Kyiv')),
          c LowCardinality(Nullable(String))
       ) ENGINE Memory
    SQL
  end

  describe 'cast' do
    before do
      subject.execute <<~SQL
        INSERT INTO rspec VALUES (
          now(),
          now(),
          null
        );
      SQL
    end

    it 'works' do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got.fetch('a')).to be_a(Time)
      expect(got.fetch('a')).to have_attributes(zone: Time.now.zone)

      expect(got.fetch('b')).to be_a(Time)
      expect(got.fetch('b')).to have_attributes(zone: Time.find_zone('Europe/Kyiv').tzinfo.abbr)

      expect(got.fetch('c')).to be_a(NilClass)
    end
  end

  describe 'serialize' do
    let(:row) do
      {
        'a' => Time.now,
        'b' => Time.now,
        'c' => nil,
      }
    end

    it 'works' do
      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))
      got = subject.select_one('SELECT * FROM rspec')

      expect(got.fetch('a')).to be_a(Time)
      expect(got.fetch('b')).to be_a(Time)
      expect(got.fetch('c')).to be_a(NilClass)
    end
  end
end


================================================
FILE: spec/click_house/integration/map_spec.rb
================================================
# frozen_string_literal: true

RSpec.describe ClickHouse::Type::MapType do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
          a Map(LowCardinality(String), Array(DateTime('Europe/Kyiv'))),
          b Map(Int8, IPv4)
       ) ENGINE Memory
    SQL
  end

  describe 'cast' do
    before do
      subject.execute <<~SQL
        INSERT INTO rspec VALUES (
          {'foo': ['2019-01-01']}, 
          {1: '127.0.0.1'}
        )
      SQL
    end

    it 'works' do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got.dig('a', 'foo')).to eq([Time.find_zone('Europe/Kyiv').parse('2019-01-01')])
      expect(got.dig('b', 1)).to eq(IPAddr.new('127.0.0.1'))
    end
  end

  describe 'serialize' do
    let(:row) do
      {
        'a' => {'foo' => [Time.find_zone('Europe/Kyiv').now.round]},
        'b' => { 1 => IPAddr.new('127.0.0.1')},
      }
    end

    it 'works' do
      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))
      got = subject.select_one('SELECT * FROM rspec')

      expect(got.fetch('a')).to eq(row.fetch('a'))
      expect(got.fetch('b')).to eq(row.fetch('b'))
    end
  end
end


================================================
FILE: spec/click_house/integration/nested_spec.rb
================================================
# frozen_string_literal: true

RSpec.describe 'Nested' do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
        json Nested(
          a Date
        ) 
       ) ENGINE Memory
    SQL
  end

  describe 'cast' do
    before do
      subject.execute <<~SQL
        INSERT INTO rspec VALUES (
          (['2022-01-01']) 
        )
      SQL
    end

    it 'works' do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got.fetch('json.a')).to eq([Date.new(2022, 1, 1)])
    end
  end

  describe 'serialize' do
    let(:row) do
      {
        'json.a' => [Date.new(2022, 1, 2)]
      }
    end

    it 'works' do
      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))
      got = subject.select_one('SELECT * FROM rspec')

      expect(got.fetch('json.a')).to eq(row.fetch('json.a'))
    end
  end
end


================================================
FILE: spec/click_house/integration/string_spec.rb
================================================
# frozen_string_literal: true

RSpec.describe ClickHouse::Type::StringType do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
          a String,
          b FixedString(2),
          c UUID, 
          d Nullable(UUID)
       ) ENGINE Memory
    SQL
  end

  describe 'cast' do
    before do
      subject.execute <<~SQL
        INSERT INTO rspec VALUES (
            'x',
            'y',
            'da70495b-1ff7-49e5-8feb-d657bd4ea1ea',
            NULL
        );
      SQL
    end

    it 'works' do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got.fetch('a')).to eq("x")
      expect(got.fetch('b')).to eq("y\u0000")
      expect(got.fetch('c')).to eq("da70495b-1ff7-49e5-8feb-d657bd4ea1ea")
      expect(got.fetch('d')).to eq(nil)
    end
  end

  describe 'serialize' do
    let(:row) do
      {
        'a' => 'foo',
        'b' => 'xe',
        'c' => "da70495b-1ff7-49e5-8feb-d657bd4ea1ea",
        'd' => nil
      }
    end

    it 'works' do
      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))
      got = subject.select_one('SELECT * FROM rspec')

      expect(got.fetch('a')).to eq(row.fetch('a'))
      expect(got.fetch('b')).to eq(row.fetch('b'))
      expect(got.fetch('c')).to eq(row.fetch('c'))
      expect(got.fetch('d')).to eq(row.fetch('d'))
    end
  end
end


================================================
FILE: spec/click_house/integration/symbolize_keys_spec.rb
================================================
# frozen_string_literal: true

RSpec.describe ClickHouse::Config do
  subject do
    ClickHouse::Connection.new(ClickHouse.config.clone.assign(symbolize_keys: true))
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
          a String,
      ) ENGINE Memory
    SQL
  end

  describe 'cast' do
    before do
      subject.execute <<~SQL
        INSERT INTO rspec VALUES (
           'foo'
        );
      SQL
    end

    it 'works' do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got).to eq({a: "foo"})

      got = subject.select_value('SELECT * FROM rspec')
      expect(got).to eq("foo")

      got = subject.select_all('SELECT * FROM rspec')
      expect(got.meta).to eq([{:name=>"a", :type=>"String"}])
      expect(got.statistics).to include(rows_read: 1)
    end
  end

  describe 'serialize' do
    let(:row) do
      {
        a: 'foo'
      }
    end

    it 'works' do
      subject.insert_rows('rspec', subject.table_schema('rspec').serialize_one(row))

      got = subject.select_one('SELECT * FROM rspec')
      expect(got.fetch(:a)).to eq(row.fetch(:a))
    end
  end
end


================================================
FILE: spec/click_house/integration/table_schema_spec.rb
================================================
RSpec.describe ClickHouse::Response::ResultSet do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
          a Boolean,
          b Array(Nullable(String))
       ) ENGINE Memory
    SQL
  end

  let(:schema) do
    subject.table_schema('rspec')
  end

  describe '#types' do
    it 'works' do
      expect(schema.types).to have_key('a')
      expect(schema.types).to have_key('b')
    end
  end

  describe '#serialize_column' do
    it 'works' do
      expect(schema.serialize_column('a', true)).to eq(1)
      expect(schema.serialize_column('b', [])).to eq([])
    end

    it 'errors if column missing' do
      expect { schema.serialize_column('foo', 'bar') }.to raise_error(ClickHouse::SerializeError)
    end

    it 'errors if value has improper type' do
      expect { schema.serialize_column('b', nil) }.to raise_error(ClickHouse::SerializeError)
    end
  end

  describe '#serialize_one' do
    it 'works' do
      expect(schema.serialize_one({'a' => true, 'b' => ['foo']})).to eq({'a' => 1, 'b' => ['foo']})
    end
  end

  describe '#serialize' do
    let(:row) do
      {'a' => true, 'b' => ['foo']}
    end

    let(:expectation) do
      {'a' => 1, 'b' => ['foo']}
    end

    context 'when Hash' do
      it 'works' do
        expect(schema.serialize(row)).to eq(expectation)
      end
    end

    context 'when Array' do
      it 'works' do
        expect(schema.serialize([row])).to eq([expectation])
      end
    end

    context 'when other' do
      it 'errors' do
        expect { schema.serialize(nil) }.to raise_error(ArgumentError)
      end
    end
  end
end


================================================
FILE: spec/click_house/integration/tuple_spec.rb
================================================
# frozen_string_literal: true

RSpec.describe ClickHouse::Type::TupleType do
  subject do
    ClickHouse.connection
  end

  before do
    subject.execute <<~SQL
      CREATE TABLE rspec(
          a Tuple(IPv4, Nullable(Date), Nullable(String))
       ) ENGINE Memory
    SQL
  end

  describe 'cast' do
    before do
      subject.execute <<~SQL
        INSERT INTO rspec VALUES (
          ('127.0.0.1', '2022-01-02', NULL)
        );
      SQL
    end

    let(:expectation) do
      [
        IPAddr.new('127.0.0.1'),
        Date.new(2022, 1, 2),
        nil
      ]
    end

    it 'cast type' do
      got = subject.select_one('SELECT * FROM rspec')
      expect(got.fetch('a')).to eq(expectation)
    end
  end

  describe 'serialize' do
    let(:row) do
      {
        'a' =>  [
          IPAddr.new('127.0.0.1'),
          Date.new(2022, 1, 1),
          nil
        ],
      }
    end

    it 'works' do
      subject.insert('rspec', subject.table_schema('rspec').serialize_one(row))
      got = subject.select_one('SELECT * FROM rspec')

      expect(got.fetch('a')).to eq(row.fetch('a'))
    end
  end
end


================================================
FILE: spec/click_house/response/factory_spec.rb
================================================
RSpec.describe ClickHouse::Response::Factory do
  subject do
    ClickHouse.connection
  end

  describe 'WITH totals modifier' do
    context 'when blank' do
      let(:response) do
        subject.select_all('SELECT 1')
      end

      it 'is empty' do
        expect(response.totals).to eq(nil)
      end
    end

    context 'when exists' do
      let(:response) do
        subject.select_all('SELECT SUM(1) AS s WITH TOTALS')
      end

      it 'is present' do
        expect(response.totals).to eq({ 's' => '1' })
      end
    end
  end
end


================================================
FILE: spec/click_house/type/date_time64_spec.rb
================================================
RSpec.describe ClickHouse::Type::DateTime64Type do
  let(:precisions) do 
    (0..9).to_a
  end

  describe '#serialize' do
    let(:time) do
      Time.new(2019, 1, 1, 9, 5, 6)
    end

    it 'works' do
      precisions.each do |precision|        
        tail = "." + "0" * precision if precision > 0
        expect(subject.serialize(time, precision)).to eq("2019-01-01 09:05:06#{tail}")
      end
    end
  end

  describe '#cast' do
    context 'when zone is empty' do
      let(:time) do
        Time.new(2019, 1, 1, 9, 5, 6)
      end

      it 'works' do
        expect(subject.cast('2019-01-01 09:05:06.0000')).to eq(time)
      end
    end

    context 'when zone exists' do
      let(:time) do
        Time.new(2019, 1, 1, 9, 5, 6, Time.find_zone('Europe/Kyiv'))
      end

      it 'works' do
        expect(subject.cast('2019-01-01 09:05:06', 0, 'Europe/Kyiv').to_s).to eq(time.to_s)
        expect(subject.cast('2019-01-01 09:05:06.00', 9, 'Europe/Kyiv').to_s).to eq(time.to_s)
      end
    end
  end
end


================================================
FILE: spec/click_house/type/date_time_type_spec.rb
================================================
RSpec.describe ClickHouse::Type::DateTimeType do
  describe '#serialize' do
    let(:time) do
      Time.new(2019, 1, 1, 9, 5, 6)
    end

    it 'works' do
      expect(subject.serialize(time)).to eq('2019-01-01 09:05:06')
    end
  end

  describe '#cast' do
    context 'when zone is empty' do
      let(:time) do
        Time.new(2019, 1, 1, 9, 5, 6)
      end

      it 'works' do
        expect(subject.cast('2019-01-01 09:05:06')).to eq(time)
      end
    end

    context 'when zone exists' do
      let(:time) do
        Time.new(2019, 1, 1, 9, 5, 6, Time.find_zone('Europe/Kyiv'))
      end

      it 'works' do
        expect(subject.cast('2019-01-01 09:05:06', 'Europe/Kyiv')).to eq(time)
      end
    end
  end
end


================================================
FILE: spec/click_house/type/decimal_type_spec.rb
================================================
RSpec.describe ClickHouse::Type::DecimalType do
  describe '#casr' do
    context 'when String' do
      it 'works' do
        expect(subject.cast("1.0")).to eq(BigDecimal(1.0, 1))
      end
    end

    context 'when BigDecimal' do
      it 'works' do
        expect(subject.cast(BigDecimal(1))).to eq(BigDecimal(1))
      end
    end

    context 'when Float' do
      it 'works' do
        expect(subject.cast(1.0, 1)).to eq(BigDecimal(1.0, 1))
        expect(subject.cast(1.0)).to eq(BigDecimal(1.0, ClickHouse::Type::DecimalType::MAXIMUM))
      end
    end
  end
end


================================================
FILE: spec/click_house/type/fixed_string_type_spec.rb
================================================
RSpec.describe ClickHouse::Type::FixedStringType do
  describe '#serialize' do
    def target(value, limit = nil)
      described_class.new.serialize(value, limit)
    end

    it 'works' do
      expect(target(nil)).to eq(nil)
      expect(target('foo bar')).to eq('foo bar')
      expect(target('foo bar', 1)).to eq('f')
      expect(target('foo bar', 2)).to eq('fo')
    end
  end
end


================================================
FILE: spec/click_house/type/float_type_spec.rb
================================================
RSpec.describe ClickHouse::Type::FloatType do
  describe '#serialize' do
    it 'works' do
      expect(subject.serialize(5)).to be_a(Float)
      expect(subject.serialize(5)).to eq(5.0)
      expect(subject.serialize(5.0)).to eq(5.0)
      expect(subject.serialize(nil)).to eq(nil)
    end
  end

  describe '#cast' do
    it 'works' do
      expect(subject.cast(5)).to be_a(Float)
      expect(subject.cast(5)).to eq(5.0)
      expect(subject.cast(5.0)).to eq(5.0)
      expect(subject.cast(nil)).to eq(nil)
    end
  end
end


================================================
FILE: spec/click_house/type/ip_type_spec.rb
================================================
RSpec.describe ClickHouse::Type::IPType do
  describe '#cast' do
    let(:ip) do
      IPAddr.new('127.0.0.1')
    end

    it 'works' do
      expect(subject.cast(String(ip))).to eq(ip)
    end
  end

  describe '#serialize' do
    let(:ip) do
      IPAddr.new('127.0.0.1')
    end

    it 'works' do
      expect(subject.serialize(ip)).to eq('127.0.0.1')
    end
  end
end


================================================
FILE: spec/click_house/util/pretty_spec.rb
================================================
RSpec.describe ClickHouse::Util::Pretty do
  describe '#size' do
    let(:expectation) do
      {
        0 => '0B',
        100 => '100.0B',
        1456 => '1.4KiB',
        1024000 * 2 => '2.0MiB',
        10737418240 => '10.0GiB',
        10737418240 * 1_000 => '9.8TiB',
      }
    end

    it 'works' do
      expectation.each do |bytes, pretty|
        expect(described_class.size(bytes)).to eq(pretty)
      end
    end
  end
end


================================================
FILE: spec/oj_helper.rb
================================================
# frozen_string_literal: tru

RSpec.configure do |config|
  config.before(:each) do
    ClickHouse.config do |c|
      c.json_parser = ClickHouse::Middleware::ParseJsonOj
      c.json_serializer = ClickHouse::Serializer::JsonOjSerializer
    end
  end
end


================================================
FILE: spec/spec_helper.rb
================================================
# frozen_string_literal: true

$ROOT_PATH = File.expand_path('../../', __FILE__).freeze

require 'bundler/setup'
require 'click_house'
require 'pry'

Dir[File.join($ROOT_PATH, 'spec', 'support', '*.rb')].each { |f| require f }

ClickHouse.config do |config|
  config.logger = Logger.new('log/test.log', level: Logger::DEBUG)
  config.database = 'click_house_rspec'
  config.url = 'http://localhost:8123?allow_suspicious_low_cardinality_types=1&output_format_arrow_low_cardinality_as_dictionary=1'
end

RSpec.configure do |config|
  config.example_status_persistence_file_path = '.rspec_status'
  config.disable_monkey_patching!
  config.filter_run :focus
  config.run_all_when_everything_filtered = true
  config.order = :random
  Kernel.srand config.seed

  config.expect_with :rspec do |c|
    c.syntax = :expect
  end
end


================================================
FILE: spec/support/database_cleaner.rb
================================================
SYSTEM_DATABASES = %w[default system _temporary_and_external_tables]

RSpec.configure do |config|
  config.around(:each) do |example|
    ClickHouse.connection.create_database(ClickHouse.config.database, if_not_exists: true)

    example.run

    ClickHouse.connection.databases.each do |database|
      next if SYSTEM_DATABASES.include?(database)

      ClickHouse.connection.drop_database(database, if_exists: true)
    end
  end
end


================================================
FILE: spec/support/reset_connection.rb
================================================
# frozen_string_literal: tru

RSpec.configure do |config|
  config.around(:each) do |example|
    ClickHouse.connection = nil
    example.run
  end
end


================================================
FILE: spec/support/ruby_version.rb
================================================
# frozen_string_literal: true

# @return [Boolean]
# @param version [String] like "2.7"
def ruby_version_gt(version)
  Gem::Version.new(RUBY_VERSION) > Gem::Version.new(version)
end

# @return [Boolean]
# @param version [String] like "2.7"
def ruby_version_lt(version)
  Gem::Version.new(RUBY_VERSION) < Gem::Version.new(version)
end


================================================
FILE: tmp/.keep
================================================

Download .txt
gitextract_7a4m72cg/

├── .github/
│   └── workflows/
│       └── main.yml
├── .gitignore
├── .rspec
├── .rubocop.yml
├── CHANGELOG.md
├── Gemfile
├── Gemfile_faraday1
├── LICENCE.txt
├── Makefile
├── README.md
├── Rakefile
├── bin/
│   ├── console
│   ├── release.sh
│   └── setup
├── click_house.gemspec
├── docker-compose.yml
├── lib/
│   ├── click_house/
│   │   ├── ast/
│   │   │   ├── parser.rb
│   │   │   ├── statement.rb
│   │   │   └── ticker.rb
│   │   ├── ast.rb
│   │   ├── benchmark/
│   │   │   ├── casting.rb
│   │   │   └── map_join.rb
│   │   ├── config.rb
│   │   ├── connection.rb
│   │   ├── definition/
│   │   │   ├── column.rb
│   │   │   └── column_set.rb
│   │   ├── definition.rb
│   │   ├── errors.rb
│   │   ├── extend/
│   │   │   ├── configurable.rb
│   │   │   ├── connectible.rb
│   │   │   ├── connection_altering.rb
│   │   │   ├── connection_database.rb
│   │   │   ├── connection_explaining.rb
│   │   │   ├── connection_healthy.rb
│   │   │   ├── connection_inserting.rb
│   │   │   ├── connection_selective.rb
│   │   │   ├── connection_table.rb
│   │   │   └── type_definition.rb
│   │   ├── extend.rb
│   │   ├── middleware/
│   │   │   ├── logging.rb
│   │   │   ├── parse_csv.rb
│   │   │   ├── parse_json.rb
│   │   │   ├── parse_json_oj.rb
│   │   │   ├── raise_error.rb
│   │   │   ├── response_base.rb
│   │   │   └── summary_middleware.rb
│   │   ├── middleware.rb
│   │   ├── response/
│   │   │   ├── factory.rb
│   │   │   ├── result_set.rb
│   │   │   └── summary.rb
│   │   ├── response.rb
│   │   ├── serializer/
│   │   │   ├── base.rb
│   │   │   ├── json_oj_serializer.rb
│   │   │   └── json_serializer.rb
│   │   ├── serializer.rb
│   │   ├── type/
│   │   │   ├── array_type.rb
│   │   │   ├── base_type.rb
│   │   │   ├── boolean_type.rb
│   │   │   ├── date_time64_type.rb
│   │   │   ├── date_time_type.rb
│   │   │   ├── date_type.rb
│   │   │   ├── decimal_type.rb
│   │   │   ├── fixed_string_type.rb
│   │   │   ├── float_type.rb
│   │   │   ├── integer_type.rb
│   │   │   ├── ip_type.rb
│   │   │   ├── low_cardinality_type.rb
│   │   │   ├── map_type.rb
│   │   │   ├── nullable_type.rb
│   │   │   ├── string_type.rb
│   │   │   ├── tuple_type.rb
│   │   │   └── undefined_type.rb
│   │   ├── type.rb
│   │   ├── util/
│   │   │   ├── pretty.rb
│   │   │   └── statement.rb
│   │   ├── util.rb
│   │   └── version.rb
│   └── click_house.rb
├── log/
│   └── .keep
├── spec/
│   ├── click_house/
│   │   ├── ast/
│   │   │   └── parser_spec.rb
│   │   ├── config_spec.rb
│   │   ├── connection_spec.rb
│   │   ├── definition/
│   │   │   └── column_set_spec.rb
│   │   ├── extend/
│   │   │   ├── connection_altering_spec.rb
│   │   │   ├── connection_database_spec.rb
│   │   │   ├── connection_explaining_spec.rb
│   │   │   ├── connection_healthy_spec.rb
│   │   │   ├── connection_inserting_spec.rb
│   │   │   ├── connection_selective_spec.rb
│   │   │   └── connection_table_spec.rb
│   │   ├── integration/
│   │   │   ├── array_spec.rb
│   │   │   ├── boolean_type_spec.rb
│   │   │   ├── date_spec.rb
│   │   │   ├── date_time64_spec.rb
│   │   │   ├── date_time_spec.rb
│   │   │   ├── decimal_spec.rb
│   │   │   ├── enum_spec.rb
│   │   │   ├── float_spec.rb
│   │   │   ├── formats.rb
│   │   │   ├── function_spec.rb
│   │   │   ├── integer_spec.rb
│   │   │   ├── ip_spec.rb
│   │   │   ├── loggin_spec.rb
│   │   │   ├── low_cardinality_spec.rb
│   │   │   ├── map_spec.rb
│   │   │   ├── nested_spec.rb
│   │   │   ├── string_spec.rb
│   │   │   ├── symbolize_keys_spec.rb
│   │   │   ├── table_schema_spec.rb
│   │   │   └── tuple_spec.rb
│   │   ├── response/
│   │   │   └── factory_spec.rb
│   │   ├── type/
│   │   │   ├── date_time64_spec.rb
│   │   │   ├── date_time_type_spec.rb
│   │   │   ├── decimal_type_spec.rb
│   │   │   ├── fixed_string_type_spec.rb
│   │   │   ├── float_type_spec.rb
│   │   │   └── ip_type_spec.rb
│   │   └── util/
│   │       └── pretty_spec.rb
│   ├── oj_helper.rb
│   ├── spec_helper.rb
│   └── support/
│       ├── database_cleaner.rb
│       ├── reset_connection.rb
│       └── ruby_version.rb
└── tmp/
    └── .keep
Download .txt
SYMBOL INDEX (360 symbols across 64 files)

FILE: lib/click_house.rb
  type ClickHouse (line 23) | module ClickHouse

FILE: lib/click_house/ast.rb
  type ClickHouse (line 3) | module ClickHouse
    type Ast (line 4) | module Ast

FILE: lib/click_house/ast/parser.rb
  type ClickHouse (line 5) | module ClickHouse
    type Ast (line 6) | module Ast
      class Parser (line 7) | class Parser
        method initialize (line 16) | def initialize(input)
        method parse (line 23) | def parse

FILE: lib/click_house/ast/statement.rb
  type ClickHouse (line 5) | module ClickHouse
    type Ast (line 6) | module Ast
      class Statement (line 7) | class Statement
        method initialize (line 15) | def initialize(name: '')
        method print (line 21) | def print(value)
        method name! (line 25) | def name!
        method argument! (line 30) | def argument!
        method add_argument (line 36) | def add_argument(st)
        method merge (line 41) | def merge(other)
        method named? (line 49) | def named?
        method buffer? (line 53) | def buffer?
        method arguments (line 58) | def arguments
        method argument_values (line 64) | def argument_values
        method argument_first! (line 68) | def argument_first!
        method placeholder (line 73) | def placeholder
        method digit? (line 79) | def digit?
        method value (line 83) | def value
        method to_s (line 96) | def to_s

FILE: lib/click_house/ast/ticker.rb
  type ClickHouse (line 5) | module ClickHouse
    type Ast (line 6) | module Ast
      class Ticker (line 7) | class Ticker
        method initialize (line 10) | def initialize
        method open (line 14) | def open
        method comma (line 20) | def comma
        method close (line 26) | def close
        method char (line 33) | def char(char)
        method opened (line 37) | def opened

FILE: lib/click_house/config.rb
  type ClickHouse (line 3) | module ClickHouse
    class Config (line 4) | class Config
      method initialize (line 59) | def initialize(params = {})
      method assign (line 65) | def assign(params = {})
      method auth? (line 71) | def auth?
      method logger! (line 75) | def logger!
      method url! (line 79) | def url!
      method null_logger (line 83) | def null_logger
      method json_serializer= (line 88) | def json_serializer=(klass)
      method symbolize_keys= (line 92) | def symbolize_keys=(value)
      method key (line 103) | def key(name)

FILE: lib/click_house/connection.rb
  type ClickHouse (line 3) | module ClickHouse
    class Connection (line 4) | class Connection
      method initialize (line 16) | def initialize(config)
      method execute (line 20) | def execute(query, body = nil, database: config.database, params: {})
      method get (line 30) | def get(path = '/', body: '', query: {}, database: config.database)
      method post (line 45) | def post(body = nil, query: {}, database: config.database, params: {})
      method transport (line 51) | def transport
      method compose (line 76) | def compose(path, query = {})
      method faraday_v1? (line 82) | def faraday_v1?

FILE: lib/click_house/definition.rb
  type ClickHouse (line 3) | module ClickHouse
    type Definition (line 4) | module Definition

FILE: lib/click_house/definition/column.rb
  type ClickHouse (line 3) | module ClickHouse
    type Definition (line 4) | module Definition
      class Column (line 5) | class Column
        method initialize (line 15) | def initialize(params = {})
        method to_s (line 20) | def to_s
        method opts (line 28) | def opts
        method extension_type (line 42) | def extension_type

FILE: lib/click_house/definition/column_set.rb
  type ClickHouse (line 3) | module ClickHouse
    type Definition (line 4) | module Definition
      class ColumnSet (line 5) | class ColumnSet
        method method_name_for_type (line 13) | def method_name_for_type(type)
        method initialize (line 36) | def initialize
        method columns (line 40) | def columns
        method to_s (line 44) | def to_s
        method nested (line 54) | def nested(name, &block)
        method push (line 60) | def push(sql)

FILE: lib/click_house/errors.rb
  type ClickHouse (line 3) | module ClickHouse

FILE: lib/click_house/extend.rb
  type ClickHouse (line 3) | module ClickHouse
    type Extend (line 4) | module Extend

FILE: lib/click_house/extend/configurable.rb
  type ClickHouse (line 3) | module ClickHouse
    type Extend (line 4) | module Extend
      type Configurable (line 5) | module Configurable
        function config (line 6) | def config(&block)

FILE: lib/click_house/extend/connectible.rb
  type ClickHouse (line 3) | module ClickHouse
    type Extend (line 4) | module Extend
      type Connectible (line 5) | module Connectible
        function connection= (line 6) | def connection=(connection)
        function connection (line 10) | def connection

FILE: lib/click_house/extend/connection_altering.rb
  type ClickHouse (line 4) | module ClickHouse
    type Extend (line 5) | module Extend
      type ConnectionAltering (line 6) | module ConnectionAltering
        function add_column (line 7) | def add_column(table, name, type, default: nil, if_not_exists: fal...
        function drop_column (line 21) | def drop_column(table, name, if_exists: false, cluster: nil)
        function clear_column (line 32) | def clear_column(table, name, partition:, if_exists: false, cluste...
        function modify_column (line 44) | def modify_column(table, name, type: nil, default: nil, if_exists:...
        function alter_table (line 57) | def alter_table(name, sql = nil, cluster: nil)
        function add_index (line 70) | def add_index(
        function drop_index (line 91) | def drop_index(table_name, name, cluster: nil)

FILE: lib/click_house/extend/connection_database.rb
  type ClickHouse (line 3) | module ClickHouse
    type Extend (line 4) | module Extend
      type ConnectionDatabase (line 5) | module ConnectionDatabase
        function databases (line 7) | def databases
        function create_database (line 11) | def create_database(name, if_not_exists: false, cluster: nil, engi...
        function drop_database (line 24) | def drop_database(name, if_exists: false, cluster: nil)

FILE: lib/click_house/extend/connection_explaining.rb
  type ClickHouse (line 3) | module ClickHouse
    type Extend (line 4) | module Extend
      type ConnectionExplaining (line 5) | module ConnectionExplaining
        function explain (line 10) | def explain(sql, io: StringIO.new)

FILE: lib/click_house/extend/connection_healthy.rb
  type ClickHouse (line 3) | module ClickHouse
    type Extend (line 4) | module Extend
      type ConnectionHealthy (line 5) | module ConnectionHealthy
        function ping (line 6) | def ping
        function replicas_status (line 10) | def replicas_status

FILE: lib/click_house/extend/connection_inserting.rb
  type ClickHouse (line 3) | module ClickHouse
    type Extend (line 4) | module Extend
      type ConnectionInserting (line 5) | module ConnectionInserting
        function insert (line 20) | def insert(table, body = [], **opts)
        function insert_rows (line 61) | def insert_rows(table, body, format: nil)
        function insert_compact (line 75) | def insert_compact(table, columns: [], values: [], format: nil)

FILE: lib/click_house/extend/connection_selective.rb
  type ClickHouse (line 3) | module ClickHouse
    type Extend (line 4) | module Extend
      type ConnectionSelective (line 5) | module ConnectionSelective
        function select_all (line 7) | def select_all(sql)
        function select_value (line 12) | def select_value(sql)
        function select_one (line 26) | def select_one(sql)

FILE: lib/click_house/extend/connection_table.rb
  type ClickHouse (line 3) | module ClickHouse
    type Extend (line 4) | module Extend
      type ConnectionTable (line 5) | module ConnectionTable
        function tables (line 7) | def tables
        function describe_table (line 12) | def describe_table(name)
        function table_schema (line 17) | def table_schema(name)
        function table_exists? (line 22) | def table_exists?(name, temporary: false)
        function drop_table (line 33) | def drop_table(name, temporary: false, if_exists: false, cluster: ...
        function create_table (line 47) | def create_table(
        function truncate_table (line 83) | def truncate_table(name, if_exists: false, cluster: nil)
        function truncate_tables (line 95) | def truncate_tables(names = tables, *argv)
        function rename_table (line 99) | def rename_table(from, to, cluster: nil)

FILE: lib/click_house/extend/type_definition.rb
  type ClickHouse (line 3) | module ClickHouse
    type Extend (line 4) | module Extend
      type TypeDefinition (line 5) | module TypeDefinition
        function types (line 6) | def types
        function add_type (line 10) | def add_type(type, klass)

FILE: lib/click_house/middleware.rb
  type ClickHouse (line 3) | module ClickHouse
    type Middleware (line 4) | module Middleware

FILE: lib/click_house/middleware/logging.rb
  type ClickHouse (line 3) | module ClickHouse
    type Middleware (line 4) | module Middleware
      class Logging (line 5) | class Logging < Faraday::Middleware
        method initialize (line 13) | def initialize(app = nil, logger:)
        method call (line 18) | def call(env)
        method on_complete (line 24) | def on_complete(env)
        method duration (line 34) | def duration
        method timestamp (line 38) | def timestamp
        method log_body? (line 43) | def log_body?(env)
        method query (line 51) | def query(env)
        method duration_stats_log (line 59) | def duration_stats_log(summary)

FILE: lib/click_house/middleware/parse_csv.rb
  type ClickHouse (line 3) | module ClickHouse
    type Middleware (line 4) | module Middleware
      class ParseCsv (line 5) | class ParseCsv < ResponseBase
        method on_complete (line 9) | def on_complete(env)

FILE: lib/click_house/middleware/parse_json.rb
  type ClickHouse (line 3) | module ClickHouse
    type Middleware (line 4) | module Middleware
      class ParseJson (line 5) | class ParseJson < ResponseBase
        method on_complete (line 9) | def on_complete(env)

FILE: lib/click_house/middleware/parse_json_oj.rb
  type ClickHouse (line 3) | module ClickHouse
    type Middleware (line 4) | module Middleware
      class ParseJsonOj (line 5) | class ParseJsonOj < ResponseBase
        method on_complete (line 9) | def on_complete(env)
        method on_setup (line 17) | def on_setup

FILE: lib/click_house/middleware/raise_error.rb
  type ClickHouse (line 3) | module ClickHouse
    type Middleware (line 4) | module Middleware
      class RaiseError (line 5) | class RaiseError < Faraday::Middleware
        method call (line 11) | def call(env)
        method on_complete (line 18) | def on_complete(env)

FILE: lib/click_house/middleware/response_base.rb
  type ClickHouse (line 3) | module ClickHouse
    type Middleware (line 4) | module Middleware
      class ResponseBase (line 5) | class ResponseBase < Faraday::Middleware
        method initialize (line 11) | def initialize(app = nil, options: {}, content_type: nil, preserve...
        method content_type? (line 22) | def content_type?(env, regexp)
        method config (line 34) | def config
        method on_setup (line 40) | def on_setup

FILE: lib/click_house/middleware/summary_middleware.rb
  type ClickHouse (line 3) | module ClickHouse
    type Middleware (line 4) | module Middleware
      class SummaryMiddleware (line 5) | class SummaryMiddleware < ResponseBase
        method extract (line 12) | def self.extract(env)
        method on_complete (line 17) | def on_complete(env)

FILE: lib/click_house/response.rb
  type ClickHouse (line 3) | module ClickHouse
    type Response (line 4) | module Response

FILE: lib/click_house/response/factory.rb
  type ClickHouse (line 3) | module ClickHouse
    type Response (line 4) | module Response
      class Factory (line 5) | class Factory
        method response (line 12) | def self.response(faraday, config)
        method raw (line 30) | def self.raw(faraday, config)
        method exec (line 41) | def self.exec(faraday)
        method empty_exec (line 46) | def self.empty_exec(config)

FILE: lib/click_house/response/result_set.rb
  type ClickHouse (line 3) | module ClickHouse
    type Response (line 4) | module Response
      class ResultSet (line 5) | class ResultSet
        method raw (line 25) | def raw(config:, data:, summary:)
        method initialize (line 34) | def initialize(config:, meta:, data:, summary:, to_a: nil)
        method serialize (line 44) | def serialize(data)
        method serialize_one (line 57) | def serialize_one(row)
        method serialize_column (line 65) | def serialize_column(name, value)
        method to_a (line 74) | def to_a
        method types (line 83) | def types
        method assign_type (line 99) | def assign_type(stmt)
        method cast_type (line 111) | def cast_type(stmt, value)
        method cast_map (line 122) | def cast_map(stmt, hash)
        method cast_container (line 132) | def cast_container(stmt, value)
        method cast_tuple (line 139) | def cast_tuple(stmt, value)
        method serialize_type (line 146) | def serialize_type(stmt, value)
        method serialize_container (line 155) | def serialize_container(stmt, value)
        method serialize_map (line 164) | def serialize_map(stmt, hash)
        method serialize_tuple (line 174) | def serialize_tuple(stmt, value)

FILE: lib/click_house/response/summary.rb
  type ClickHouse (line 3) | module ClickHouse
    type Response (line 4) | module Response
      class Summary (line 5) | class Summary
        method initialize (line 26) | def initialize(config, headers: Faraday::Utils::Headers.new, body:...
        method read_rows (line 36) | def read_rows
        method read_bytes (line 41) | def read_bytes
        method read_bytes_pretty (line 46) | def read_bytes_pretty
        method written_rows (line 51) | def written_rows
        method written_bytes (line 56) | def written_bytes
        method written_bytes_pretty (line 61) | def written_bytes_pretty
        method total_rows_to_read (line 66) | def total_rows_to_read
        method result_rows (line 71) | def result_rows
        method result_bytes (line 76) | def result_bytes
        method elapsed (line 81) | def elapsed
        method elapsed_pretty (line 86) | def elapsed_pretty
        method parse_summary (line 102) | def parse_summary(value)

FILE: lib/click_house/serializer.rb
  type ClickHouse (line 3) | module ClickHouse
    type Serializer (line 4) | module Serializer

FILE: lib/click_house/serializer/base.rb
  type ClickHouse (line 3) | module ClickHouse
    type Serializer (line 4) | module Serializer
      class Base (line 5) | class Base
        method initialize (line 9) | def initialize(config)
        method dump (line 14) | def dump(data)
        method dump_each_row (line 20) | def dump_each_row(data, sep = "\n")
        method on_setup (line 27) | def on_setup

FILE: lib/click_house/serializer/json_oj_serializer.rb
  type ClickHouse (line 3) | module ClickHouse
    type Serializer (line 4) | module Serializer
      class JsonOjSerializer (line 5) | class JsonOjSerializer < Base
        method dump (line 6) | def dump(data)
        method on_setup (line 12) | def on_setup

FILE: lib/click_house/serializer/json_serializer.rb
  type ClickHouse (line 3) | module ClickHouse
    type Serializer (line 4) | module Serializer
      class JsonSerializer (line 5) | class JsonSerializer < Base
        method dump (line 6) | def dump(data)

FILE: lib/click_house/type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type

FILE: lib/click_house/type/array_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class ArrayType (line 5) | class ArrayType < BaseType
        method cast_each (line 6) | def cast_each(value, *_argv, &block)
        method serialize_each (line 10) | def serialize_each(value, *_argv, &block)
        method container? (line 14) | def container?
        method ddl? (line 18) | def ddl?

FILE: lib/click_house/type/base_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class BaseType (line 5) | class BaseType
        method cast (line 6) | def cast(_value, *)
        method cast_each (line 10) | def cast_each(_value, *)
        method serialize_each (line 14) | def serialize_each(_value, *)
        method container? (line 20) | def container?
        method map? (line 26) | def map?
        method tuple? (line 32) | def tuple?
        method ddl? (line 38) | def ddl?

FILE: lib/click_house/type/boolean_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class BooleanType (line 5) | class BooleanType < BaseType
        method cast (line 9) | def cast(value)
        method serialize (line 18) | def serialize(value)

FILE: lib/click_house/type/date_time64_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class DateTime64Type (line 5) | class DateTime64Type < BaseType
        method cast (line 25) | def cast(value, precision = 0, tz = nil)
        method serialize (line 35) | def serialize(value, precision = 3, _tz = nil)

FILE: lib/click_house/type/date_time_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class DateTimeType (line 5) | class DateTimeType < BaseType
        method cast (line 8) | def cast(value, tz = nil)
        method serialize (line 16) | def serialize(value, *)

FILE: lib/click_house/type/date_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class DateType (line 5) | class DateType < BaseType
        method cast (line 8) | def cast(value)
        method serialize (line 12) | def serialize(value)

FILE: lib/click_house/type/decimal_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class DecimalType (line 5) | class DecimalType < BaseType
        method cast (line 13) | def cast(value, precision = MAXIMUM, _scale = nil)
        method serialize (line 25) | def serialize(value, precision = MAXIMUM, _scale = nil)

FILE: lib/click_house/type/fixed_string_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class FixedStringType (line 5) | class FixedStringType < BaseType
        method cast (line 6) | def cast(value, _limit = nil)
        method serialize (line 10) | def serialize(value, limit = nil)

FILE: lib/click_house/type/float_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class FloatType (line 5) | class FloatType < BaseType
        method cast (line 6) | def cast(value)
        method serialize (line 10) | def serialize(value)

FILE: lib/click_house/type/integer_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class IntegerType (line 5) | class IntegerType < BaseType
        method cast (line 6) | def cast(value)
        method serialize (line 10) | def serialize(value)

FILE: lib/click_house/type/ip_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class IPType (line 5) | class IPType < BaseType
        method cast (line 6) | def cast(value)
        method serialize (line 10) | def serialize(value)

FILE: lib/click_house/type/low_cardinality_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class LowCardinalityType (line 5) | class LowCardinalityType < BaseType
        method cast_each (line 6) | def cast_each(value, *_argv)
        method serialize_each (line 10) | def serialize_each(value, *_argv)
        method container? (line 14) | def container?

FILE: lib/click_house/type/map_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class MapType (line 5) | class MapType < BaseType
        method map? (line 6) | def map?
        method ddl? (line 10) | def ddl?

FILE: lib/click_house/type/nullable_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class NullableType (line 5) | class NullableType < BaseType
        method cast_each (line 6) | def cast_each(value, *_argv)
        method serialize_each (line 10) | def serialize_each(value, *_argv)
        method container? (line 14) | def container?
        method ddl? (line 18) | def ddl?

FILE: lib/click_house/type/string_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class StringType (line 5) | class StringType < BaseType
        method cast (line 6) | def cast(value, *)
        method serialize (line 10) | def serialize(value, *)

FILE: lib/click_house/type/tuple_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class TupleType (line 5) | class TupleType < BaseType
        method tuple? (line 6) | def tuple?
        method ddl? (line 10) | def ddl?

FILE: lib/click_house/type/undefined_type.rb
  type ClickHouse (line 3) | module ClickHouse
    type Type (line 4) | module Type
      class UndefinedType (line 5) | class UndefinedType < BaseType
        method cast (line 6) | def cast(value, *)
        method serialize (line 10) | def serialize(value, *)

FILE: lib/click_house/util.rb
  type ClickHouse (line 3) | module ClickHouse
    type Util (line 4) | module Util
      function array (line 11) | def array(input)

FILE: lib/click_house/util/pretty.rb
  type ClickHouse (line 3) | module ClickHouse
    type Util (line 4) | module Util
      type Pretty (line 5) | module Pretty
        function size (line 11) | def size(bytes)
        function measure (line 21) | def measure(ms)
        function squish (line 25) | def squish(string)

FILE: lib/click_house/util/statement.rb
  type ClickHouse (line 3) | module ClickHouse
    type Util (line 4) | module Util
      type Statement (line 5) | module Statement
        function ensure (line 8) | def ensure(truthful, value, fallback = nil)

FILE: lib/click_house/version.rb
  type ClickHouse (line 3) | module ClickHouse

FILE: spec/click_house/definition/column_set_spec.rb
  function squish (line 2) | def squish(string)

FILE: spec/click_house/extend/connection_inserting_spec.rb
  function expected (line 14) | def expected(insert, count)

FILE: spec/click_house/type/fixed_string_type_spec.rb
  function target (line 3) | def target(value, limit = nil)

FILE: spec/support/ruby_version.rb
  function ruby_version_gt (line 5) | def ruby_version_gt(version)
  function ruby_version_lt (line 11) | def ruby_version_lt(version)
Condensed preview — 124 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (169K chars).
[
  {
    "path": ".github/workflows/main.yml",
    "chars": 1210,
    "preview": "name: CI\n\non: [push, pull_request]\n\njobs:\n  rspec:\n    runs-on: ubuntu-latest\n\n    services:\n      clickhouse:\n        i"
  },
  {
    "path": ".gitignore",
    "chars": 155,
    "preview": "/.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# rs"
  },
  {
    "path": ".rspec",
    "chars": 53,
    "preview": "--format documentation\n--color\n--require spec_helper\n"
  },
  {
    "path": ".rubocop.yml",
    "chars": 9705,
    "preview": "require:\n  - rubocop-performance\n\nAllCops:\n  AutoCorrect: false\n  SuggestExtensions: false\n  Exclude:\n    - 'click_house"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 4155,
    "preview": "# 2.1.1\n* Fix logging with symbolized keys JSON\n* Unknown formats return raw `Response::ResultSelt` like regular JSON qu"
  },
  {
    "path": "Gemfile",
    "chars": 198,
    "preview": "# frozen_string_literal: true\n\nsource 'https://rubygems.org'\n\ngit_source(:github) { |repo_name| \"https://github.com/#{re"
  },
  {
    "path": "Gemfile_faraday1",
    "chars": 241,
    "preview": "# frozen_string_literal: true\n\nsource 'https://rubygems.org'\n\ngit_source(:github) { |repo_name| \"https://github.com/#{re"
  },
  {
    "path": "LICENCE.txt",
    "chars": 1074,
    "preview": "MIT License\n\nCOPYRIGHT (C) 2019 ALIAKSANDR SHYLAU\n\nPermission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "Makefile",
    "chars": 700,
    "preview": ".PHONY: help\n\n.BUNDLE_GEMFILE:=\n.REQUIRE:=./spec/spec_helper\n\nhelp:\n\t@echo 'Available targets:'\n\t@echo '  make dockerize"
  },
  {
    "path": "README.md",
    "chars": 16025,
    "preview": "![](./doc/logo.svg?sanitize=true)\n\n# ClickHouse Ruby driver\n\n![CI](https://github.com/shlima/click_house/workflows/CI/ba"
  },
  {
    "path": "Rakefile",
    "chars": 145,
    "preview": "# frozen_string_literal: true\n\nrequire 'bundler/gem_tasks'\nrequire 'rspec/core/rake_task'\n\nRSpec::Core::RakeTask.new(:sp"
  },
  {
    "path": "bin/console",
    "chars": 164,
    "preview": "#!/usr/bin/env ruby\n\nrequire 'bundler/setup'\nrequire 'click_house'\nrequire 'pry'\n\nClickHouse.config do |config|\n  config"
  },
  {
    "path": "bin/release.sh",
    "chars": 85,
    "preview": "#!/usr/bin/env bash\n\nrm ./*.gem\ngem build click_house.gemspec\ngem push click_house-*\n"
  },
  {
    "path": "bin/setup",
    "chars": 131,
    "preview": "#!/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 "
  },
  {
    "path": "click_house.gemspec",
    "chars": 1336,
    "preview": "lib = File.expand_path('../lib', __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequire 'click_house/"
  },
  {
    "path": "docker-compose.yml",
    "chars": 360,
    "preview": "version: '3.5'\n\nservices:\n  clickhouse:\n    image: clickhouse/clickhouse-server:22.9\n    ports:\n      - \"8123:8123\"\n    "
  },
  {
    "path": "lib/click_house/ast/parser.rb",
    "chars": 1122,
    "preview": "# frozen_string_literal: true\n\nrequire 'stringio'\n\nmodule ClickHouse\n  module Ast\n    class Parser\n      OPEN = '('\n    "
  },
  {
    "path": "lib/click_house/ast/statement.rb",
    "chars": 2227,
    "preview": "# frozen_string_literal: true\n\nrequire 'stringio'\n\nmodule ClickHouse\n  module Ast\n    class Statement\n      PLACEHOLDER_"
  },
  {
    "path": "lib/click_house/ast/ticker.rb",
    "chars": 746,
    "preview": "# frozen_string_literal: true\n\nrequire 'stringio'\n\nmodule ClickHouse\n  module Ast\n    class Ticker\n      attr_reader :ro"
  },
  {
    "path": "lib/click_house/ast.rb",
    "chars": 219,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Ast\n    autoload :Statement, 'click_house/ast/statement'\n    a"
  },
  {
    "path": "lib/click_house/benchmark/casting.rb",
    "chars": 1192,
    "preview": "# frozen_string_literal: true\n\nrequire 'bundler/setup'\nrequire 'benchmark'\nrequire 'pry'\nrequire_relative '../../click_h"
  },
  {
    "path": "lib/click_house/benchmark/map_join.rb",
    "chars": 332,
    "preview": "# frozen_string_literal: true\n\nrequire 'benchmark'\nrequire 'stringio'\n\nINPUT = Array.new(5_000_000, 'foo bar')\n\nBenchmar"
  },
  {
    "path": "lib/click_house/config.rb",
    "chars": 2757,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  class Config\n    DEFAULTS = {\n      adapter: Faraday.default_adapter,"
  },
  {
    "path": "lib/click_house/connection.rb",
    "chars": 3332,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  class Connection\n    include Extend::ConnectionHealthy\n    include Ex"
  },
  {
    "path": "lib/click_house/definition/column.rb",
    "chars": 1279,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Definition\n    class Column\n      attr_accessor :name\n      at"
  },
  {
    "path": "lib/click_house/definition/column_set.rb",
    "chars": 2022,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Definition\n    class ColumnSet\n      TYPES = ClickHouse.types."
  },
  {
    "path": "lib/click_house/definition.rb",
    "chars": 194,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Definition\n    autoload :Column, 'click_house/definition/colum"
  },
  {
    "path": "lib/click_house/errors.rb",
    "chars": 235,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  Error = Class.new(StandardError)\n  NetworkException = Class.new(Error"
  },
  {
    "path": "lib/click_house/extend/configurable.rb",
    "chars": 235,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module Configurable\n      def config(&block)\n      "
  },
  {
    "path": "lib/click_house/extend/connectible.rb",
    "chars": 268,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module Connectible\n      def connection=(connection"
  },
  {
    "path": "lib/click_house/extend/connection_altering.rb",
    "chars": 3000,
    "preview": "# frozen_string_literal: true\n\n# rubocop:disable Metrics/ParameterLists\nmodule ClickHouse\n  module Extend\n    module Con"
  },
  {
    "path": "lib/click_house/extend/connection_database.rb",
    "chars": 1162,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module ConnectionDatabase\n      # @return [Array<St"
  },
  {
    "path": "lib/click_house/extend/connection_explaining.rb",
    "chars": 372,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module ConnectionExplaining\n      EXPLAIN = 'EXPLAI"
  },
  {
    "path": "lib/click_house/extend/connection_healthy.rb",
    "chars": 275,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module ConnectionHealthy\n      def ping\n        get"
  },
  {
    "path": "lib/click_house/extend/connection_inserting.rb",
    "chars": 3000,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module ConnectionInserting\n      DEFAULT_JSON_EACH_"
  },
  {
    "path": "lib/click_house/extend/connection_selective.rb",
    "chars": 866,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module ConnectionSelective\n      # @return [ResultS"
  },
  {
    "path": "lib/click_house/extend/connection_table.rb",
    "chars": 3904,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module ConnectionTable\n      # @return [Array<Strin"
  },
  {
    "path": "lib/click_house/extend/type_definition.rb",
    "chars": 259,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    module TypeDefinition\n      def types\n        @type"
  },
  {
    "path": "lib/click_house/extend.rb",
    "chars": 789,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Extend\n    autoload :TypeDefinition, 'click_house/extend/type_"
  },
  {
    "path": "lib/click_house/middleware/logging.rb",
    "chars": 1673,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    class Logging < Faraday::Middleware\n      Farad"
  },
  {
    "path": "lib/click_house/middleware/parse_csv.rb",
    "chars": 375,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    class ParseCsv < ResponseBase\n      Faraday::Re"
  },
  {
    "path": "lib/click_house/middleware/parse_json.rb",
    "chars": 402,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    class ParseJson < ResponseBase\n      Faraday::R"
  },
  {
    "path": "lib/click_house/middleware/parse_json_oj.rb",
    "chars": 485,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    class ParseJsonOj < ResponseBase\n      Faraday:"
  },
  {
    "path": "lib/click_house/middleware/raise_error.rb",
    "chars": 650,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    class RaiseError < Faraday::Middleware\n      EX"
  },
  {
    "path": "lib/click_house/middleware/response_base.rb",
    "chars": 1040,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    class ResponseBase < Faraday::Middleware\n      "
  },
  {
    "path": "lib/click_house/middleware/summary_middleware.rb",
    "chars": 607,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    class SummaryMiddleware < ResponseBase\n      Fa"
  },
  {
    "path": "lib/click_house/middleware.rb",
    "chars": 528,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Middleware\n    autoload :ResponseBase, 'click_house/middleware"
  },
  {
    "path": "lib/click_house/response/factory.rb",
    "chars": 1464,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Response\n    class Factory\n      KEY_META = 'meta'\n      KEY_D"
  },
  {
    "path": "lib/click_house/response/result_set.rb",
    "chars": 5514,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Response\n    class ResultSet\n      extend Forwardable\n      in"
  },
  {
    "path": "lib/click_house/response/summary.rb",
    "chars": 2923,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Response\n    class Summary\n      SUMMARY_HEADER = 'x-clickhous"
  },
  {
    "path": "lib/click_house/response.rb",
    "chars": 244,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Response\n    autoload :Factory, 'click_house/response/factory'"
  },
  {
    "path": "lib/click_house/serializer/base.rb",
    "chars": 568,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Serializer\n    class Base\n      attr_reader :config\n\n      # @"
  },
  {
    "path": "lib/click_house/serializer/json_oj_serializer.rb",
    "chars": 284,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Serializer\n    class JsonOjSerializer < Base\n      def dump(da"
  },
  {
    "path": "lib/click_house/serializer/json_serializer.rb",
    "chars": 174,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Serializer\n    class JsonSerializer < Base\n      def dump(data"
  },
  {
    "path": "lib/click_house/serializer.rb",
    "chars": 276,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Serializer\n    autoload :Base, 'click_house/serializer/base'\n "
  },
  {
    "path": "lib/click_house/type/array_type.rb",
    "chars": 361,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class ArrayType < BaseType\n      def cast_each(value,"
  },
  {
    "path": "lib/click_house/type/base_type.rb",
    "chars": 806,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class BaseType\n      def cast(_value, *)\n        rais"
  },
  {
    "path": "lib/click_house/type/boolean_type.rb",
    "chars": 399,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class BooleanType < BaseType\n      TRUE_VALUE = 1\n   "
  },
  {
    "path": "lib/click_house/type/date_time64_type.rb",
    "chars": 1136,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class DateTime64Type < BaseType\n      BASE_FORMAT = '"
  },
  {
    "path": "lib/click_house/type/date_time_type.rb",
    "chars": 396,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class DateTimeType < BaseType\n      FORMAT = '%Y-%m-%"
  },
  {
    "path": "lib/click_house/type/date_type.rb",
    "chars": 276,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class DateType < BaseType\n      FORMAT = '%Y-%m-%d'\n\n"
  },
  {
    "path": "lib/click_house/type/decimal_type.rb",
    "chars": 909,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class DecimalType < BaseType\n      MAXIMUM = Float::D"
  },
  {
    "path": "lib/click_house/type/fixed_string_type.rb",
    "chars": 296,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class FixedStringType < BaseType\n      def cast(value"
  },
  {
    "path": "lib/click_house/type/float_type.rb",
    "chars": 258,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class FloatType < BaseType\n      def cast(value)\n    "
  },
  {
    "path": "lib/click_house/type/integer_type.rb",
    "chars": 226,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class IntegerType < BaseType\n      def cast(value)\n  "
  },
  {
    "path": "lib/click_house/type/ip_type.rb",
    "chars": 224,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class IPType < BaseType\n      def cast(value)\n       "
  },
  {
    "path": "lib/click_house/type/low_cardinality_type.rb",
    "chars": 304,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class LowCardinalityType < BaseType\n      def cast_ea"
  },
  {
    "path": "lib/click_house/type/map_type.rb",
    "chars": 188,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class MapType < BaseType\n      def map?\n        true\n"
  },
  {
    "path": "lib/click_house/type/nullable_type.rb",
    "chars": 374,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class NullableType < BaseType\n      def cast_each(val"
  },
  {
    "path": "lib/click_house/type/string_type.rb",
    "chars": 227,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class StringType < BaseType\n      def cast(value, *)\n"
  },
  {
    "path": "lib/click_house/type/tuple_type.rb",
    "chars": 192,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class TupleType < BaseType\n      def tuple?\n        t"
  },
  {
    "path": "lib/click_house/type/undefined_type.rb",
    "chars": 220,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    class UndefinedType < BaseType\n      def cast(value, "
  },
  {
    "path": "lib/click_house/type.rb",
    "chars": 1072,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Type\n    autoload :BaseType, 'click_house/type/base_type'\n    "
  },
  {
    "path": "lib/click_house/util/pretty.rb",
    "chars": 589,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Util\n    module Pretty\n      SIZE_UNITS = %w[B KiB MiB GiB TiB"
  },
  {
    "path": "lib/click_house/util/statement.rb",
    "chars": 221,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Util\n    module Statement\n      module_function\n\n      def ens"
  },
  {
    "path": "lib/click_house/util.rb",
    "chars": 281,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  module Util\n    autoload :Statement, 'click_house/util/statement'\n   "
  },
  {
    "path": "lib/click_house/version.rb",
    "chars": 73,
    "preview": "# frozen_string_literal: true\n\nmodule ClickHouse\n  VERSION = '2.1.2'\nend\n"
  },
  {
    "path": "lib/click_house.rb",
    "chars": 1852,
    "preview": "# frozen_string_literal: true\n\nrequire 'date'\nrequire 'json'\nrequire 'csv'\nrequire 'uri'\nrequire 'logger'\nrequire 'farad"
  },
  {
    "path": "log/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "spec/click_house/ast/parser_spec.rb",
    "chars": 1928,
    "preview": "RSpec.describe ClickHouse::Ast::Parser do\n  let(:expectations) do\n    {\n      \"Int\" => 'Int',\n      \"DateTime('Asia/Ista"
  },
  {
    "path": "spec/click_house/config_spec.rb",
    "chars": 1526,
    "preview": "RSpec.describe ClickHouse::Config do\n  describe '#assign' do\n    it 'works' do\n      expect { subject.assign(port: 33) }"
  },
  {
    "path": "spec/click_house/connection_spec.rb",
    "chars": 359,
    "preview": "RSpec.describe ClickHouse::Connection do\n  context 'when basic auth' do\n    subject do\n      ClickHouse::Connection.new("
  },
  {
    "path": "spec/click_house/definition/column_set_spec.rb",
    "chars": 1450,
    "preview": "RSpec.describe ClickHouse::Definition::ColumnSet do\n  def squish(string)\n    string.gsub(/[[:space:]]/, '').strip\n  end\n"
  },
  {
    "path": "spec/click_house/extend/connection_altering_spec.rb",
    "chars": 4283,
    "preview": "RSpec.describe ClickHouse::Extend::ConnectionAltering do\n  subject do\n    ClickHouse.connection\n  end\n\n  describe '#add_"
  },
  {
    "path": "spec/click_house/extend/connection_database_spec.rb",
    "chars": 1268,
    "preview": "RSpec.describe ClickHouse::Extend::ConnectionDatabase do\n  subject do\n    ClickHouse.connection\n  end\n\n  describe '#data"
  },
  {
    "path": "spec/click_house/extend/connection_explaining_spec.rb",
    "chars": 995,
    "preview": "RSpec.describe ClickHouse::Extend::ConnectionExplaining do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n   "
  },
  {
    "path": "spec/click_house/extend/connection_healthy_spec.rb",
    "chars": 846,
    "preview": "RSpec.describe ClickHouse::Extend::ConnectionHealthy do\n  subject do\n    ClickHouse::Connection.new(ClickHouse.config)\n "
  },
  {
    "path": "spec/click_house/extend/connection_inserting_spec.rb",
    "chars": 3443,
    "preview": "# ⚠️ INSERT IN TESTS SHOULD HAVE A DIFFERENT ORDER OF COLUMNS\n#   FROM THE ORDER IN THE TABLE ITSELF\nRSpec.describe Clic"
  },
  {
    "path": "spec/click_house/extend/connection_selective_spec.rb",
    "chars": 1667,
    "preview": "RSpec.describe ClickHouse::Extend::ConnectionSelective do\n  subject do\n    ClickHouse.connection\n  end\n\n  describe '#sel"
  },
  {
    "path": "spec/click_house/extend/connection_table_spec.rb",
    "chars": 8289,
    "preview": "RSpec.describe ClickHouse::Extend::ConnectionTable do\n  subject do\n    ClickHouse.connection\n  end\n\n  describe '#tables'"
  },
  {
    "path": "spec/click_house/integration/array_spec.rb",
    "chars": 3979,
    "preview": "RSpec.describe ClickHouse::Type::ArrayType do\n  subject do\n    ClickHouse.connection\n  end\n\n  describe 'cast' do\n    con"
  },
  {
    "path": "spec/click_house/integration/boolean_type_spec.rb",
    "chars": 1451,
    "preview": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::BooleanType do\n  subject do\n    ClickHouse.connection\n  "
  },
  {
    "path": "spec/click_house/integration/date_spec.rb",
    "chars": 1017,
    "preview": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::IPType do\n  subject do\n    ClickHouse.connection\n  end\n\n"
  },
  {
    "path": "spec/click_house/integration/date_time64_spec.rb",
    "chars": 1819,
    "preview": "RSpec.describe ClickHouse::Type::DateTime64Type do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject"
  },
  {
    "path": "spec/click_house/integration/date_time_spec.rb",
    "chars": 1782,
    "preview": "RSpec.describe ClickHouse::Type::DateTimeType do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.e"
  },
  {
    "path": "spec/click_house/integration/decimal_spec.rb",
    "chars": 1863,
    "preview": "RSpec.describe ClickHouse::Type::DecimalType do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject.ex"
  },
  {
    "path": "spec/click_house/integration/enum_spec.rb",
    "chars": 981,
    "preview": "# frozen_string_literal: true\n\nRSpec.describe 'Enum' do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    su"
  },
  {
    "path": "spec/click_house/integration/float_spec.rb",
    "chars": 1145,
    "preview": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::FloatType do\n  subject do\n    ClickHouse.connection\n  en"
  },
  {
    "path": "spec/click_house/integration/formats.rb",
    "chars": 1168,
    "preview": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Extend::ConnectionSelective do\n  subject do\n    ClickHouse.con"
  },
  {
    "path": "spec/click_house/integration/function_spec.rb",
    "chars": 419,
    "preview": "RSpec.describe 'Functions' do\n  subject do\n    ClickHouse.connection\n  end\n\n  let(:expectations) do \n    {\n      'select"
  },
  {
    "path": "spec/click_house/integration/integer_spec.rb",
    "chars": 1970,
    "preview": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::IntegerType do\n  subject do\n    ClickHouse.connection\n  "
  },
  {
    "path": "spec/click_house/integration/ip_spec.rb",
    "chars": 1410,
    "preview": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::IPType do\n  subject do\n    ClickHouse.connection\n  end\n\n"
  },
  {
    "path": "spec/click_house/integration/loggin_spec.rb",
    "chars": 828,
    "preview": "RSpec.describe ClickHouse::Middleware::Logging do\n  subject do\n    ClickHouse::Connection.new(ClickHouse.config.clone.as"
  },
  {
    "path": "spec/click_house/integration/low_cardinality_spec.rb",
    "chars": 1348,
    "preview": "RSpec.describe ClickHouse::Type::LowCardinalityType do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    sub"
  },
  {
    "path": "spec/click_house/integration/map_spec.rb",
    "chars": 1215,
    "preview": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::MapType do\n  subject do\n    ClickHouse.connection\n  end\n"
  },
  {
    "path": "spec/click_house/integration/nested_spec.rb",
    "chars": 906,
    "preview": "# frozen_string_literal: true\n\nRSpec.describe 'Nested' do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    "
  },
  {
    "path": "spec/click_house/integration/string_spec.rb",
    "chars": 1401,
    "preview": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::StringType do\n  subject do\n    ClickHouse.connection\n  e"
  },
  {
    "path": "spec/click_house/integration/symbolize_keys_spec.rb",
    "chars": 1133,
    "preview": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Config do\n  subject do\n    ClickHouse::Connection.new(ClickHou"
  },
  {
    "path": "spec/click_house/integration/table_schema_spec.rb",
    "chars": 1653,
    "preview": "RSpec.describe ClickHouse::Response::ResultSet do\n  subject do\n    ClickHouse.connection\n  end\n\n  before do\n    subject."
  },
  {
    "path": "spec/click_house/integration/tuple_spec.rb",
    "chars": 1121,
    "preview": "# frozen_string_literal: true\n\nRSpec.describe ClickHouse::Type::TupleType do\n  subject do\n    ClickHouse.connection\n  en"
  },
  {
    "path": "spec/click_house/response/factory_spec.rb",
    "chars": 550,
    "preview": "RSpec.describe ClickHouse::Response::Factory do\n  subject do\n    ClickHouse.connection\n  end\n\n  describe 'WITH totals mo"
  },
  {
    "path": "spec/click_house/type/date_time64_spec.rb",
    "chars": 1020,
    "preview": "RSpec.describe ClickHouse::Type::DateTime64Type do\n  let(:precisions) do \n    (0..9).to_a\n  end\n\n  describe '#serialize'"
  },
  {
    "path": "spec/click_house/type/date_time_type_spec.rb",
    "chars": 730,
    "preview": "RSpec.describe ClickHouse::Type::DateTimeType do\n  describe '#serialize' do\n    let(:time) do\n      Time.new(2019, 1, 1,"
  },
  {
    "path": "spec/click_house/type/decimal_type_spec.rb",
    "chars": 573,
    "preview": "RSpec.describe ClickHouse::Type::DecimalType do\n  describe '#casr' do\n    context 'when String' do\n      it 'works' do\n "
  },
  {
    "path": "spec/click_house/type/fixed_string_type_spec.rb",
    "chars": 388,
    "preview": "RSpec.describe ClickHouse::Type::FixedStringType do\n  describe '#serialize' do\n    def target(value, limit = nil)\n      "
  },
  {
    "path": "spec/click_house/type/float_type_spec.rb",
    "chars": 528,
    "preview": "RSpec.describe ClickHouse::Type::FloatType do\n  describe '#serialize' do\n    it 'works' do\n      expect(subject.serializ"
  },
  {
    "path": "spec/click_house/type/ip_type_spec.rb",
    "chars": 375,
    "preview": "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"
  },
  {
    "path": "spec/click_house/util/pretty_spec.rb",
    "chars": 439,
    "preview": "RSpec.describe ClickHouse::Util::Pretty do\n  describe '#size' do\n    let(:expectation) do\n      {\n        0 => '0B',\n   "
  },
  {
    "path": "spec/oj_helper.rb",
    "chars": 256,
    "preview": "# frozen_string_literal: tru\n\nRSpec.configure do |config|\n  config.before(:each) do\n    ClickHouse.config do |c|\n      c"
  },
  {
    "path": "spec/spec_helper.rb",
    "chars": 825,
    "preview": "# frozen_string_literal: true\n\n$ROOT_PATH = File.expand_path('../../', __FILE__).freeze\n\nrequire 'bundler/setup'\nrequire"
  },
  {
    "path": "spec/support/database_cleaner.rb",
    "chars": 436,
    "preview": "SYSTEM_DATABASES = %w[default system _temporary_and_external_tables]\n\nRSpec.configure do |config|\n  config.around(:each)"
  },
  {
    "path": "spec/support/reset_connection.rb",
    "chars": 152,
    "preview": "# frozen_string_literal: tru\n\nRSpec.configure do |config|\n  config.around(:each) do |example|\n    ClickHouse.connection "
  },
  {
    "path": "spec/support/ruby_version.rb",
    "chars": 334,
    "preview": "# frozen_string_literal: true\n\n# @return [Boolean]\n# @param version [String] like \"2.7\"\ndef ruby_version_gt(version)\n  G"
  },
  {
    "path": "tmp/.keep",
    "chars": 1,
    "preview": "\n"
  }
]

About this extraction

This page contains the full source code of the shlima/click_house GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 124 files (151.3 KB), approximately 45.2k tokens, and a symbol index with 360 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!