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
================================================

# ClickHouse Ruby driver

[](https://codeclimate.com/github/shlima/click_house)
[](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[1m[35mSQL (#{duration_stats_log(summary)})\e[0m #{query(env)};")
logger.debug(env.request_body) if log_body?(env)
logger.info("\e[1m[36mRead: #{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
================================================
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
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": "\n\n# ClickHouse Ruby driver\n\n\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.