activerecord-clickhouse-adapter 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 961d1de0d406b53d1244395ce2159a5629a335eda4f74e71b5bcdbf489c3ed01
|
|
4
|
+
data.tar.gz: e7022e7db80a7520c083c88fd058d98ddb79d0c4d8450628ba954304a104dbaf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3b8e3c624094591a222e1fc8bdc0f74abf8a8681bc9732c08b1c587b68a57f89273dbd1fb7f551e40d6ac8aa6f890cc98e531a329c4be45fa2b7a0db1efd458f
|
|
7
|
+
data.tar.gz: 2598a56859fd5b5c1882da961b8d333fe32e8131abea8173c176a0765cb7df49e733b3a037781cae349c0bc24fa191c50b5a2e5eaf55bb7f15dba8b0ee14cf57
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
# 0.2.1 (2026-07-24)
|
|
2
|
+
|
|
3
|
+
Fixed:
|
|
4
|
+
|
|
5
|
+
- Fixed comment hoisting corrupting `INSERT ... VALUES` when a string value contains an
|
|
6
|
+
unclosed `/*` (e.g. a request path of `/*`). The trailing-comment scan is now
|
|
7
|
+
string-literal aware, so a `/*` inside a value is never taken for the start of a trailing
|
|
8
|
+
sqlcommenter tag. Previously such a value mangled the statement and the server rejected the
|
|
9
|
+
whole batch with a `SYNTAX_ERROR`, dropping every row in it
|
|
10
|
+
|
|
1
11
|
# 0.2.0 (2026-07-23)
|
|
2
12
|
|
|
3
13
|
Breaking-ish (schema diff noise, not data changes):
|
data/README.md
CHANGED
|
@@ -8,6 +8,7 @@ A fully featured Active Record adapter for [ClickHouse](https://clickhouse.com)
|
|
|
8
8
|
- OLAP query surface: `FINAL`, `PREWHERE`, `SAMPLE`, `LIMIT BY`, time bucketing, approximate aggregates
|
|
9
9
|
- Real instrumentation: rows read, bytes read, and server elapsed time on every query
|
|
10
10
|
- Fast wire: RowBinary reads and chunked streaming inserts
|
|
11
|
+
- Multi-replica failover and server-enforced read-only connections
|
|
11
12
|
|
|
12
13
|
Tested against a live ClickHouse server only — no mocked responses, ever.
|
|
13
14
|
|
|
@@ -80,6 +81,8 @@ end
|
|
|
80
81
|
|
|
81
82
|
Columns are non-nullable by default, matching ClickHouse. Use `null: true` for `Nullable(...)`.
|
|
82
83
|
|
|
84
|
+
`t.datetime` defaults to precision 6 (microseconds, Rails' convention); pass `precision: nil` for the second-precision base `DateTime`. `t.binary`/`t.blob` map to `String` — ClickHouse strings are arbitrary byte sequences.
|
|
85
|
+
|
|
83
86
|
Rails-style id tables also work — `id: :bigint` (plain Int64, no autoincrement) or `id: :uuid` creates the pk column as its own sorting key, ids arrive client-generated, and models auto-detect the primary key:
|
|
84
87
|
|
|
85
88
|
```ruby
|
|
@@ -298,6 +301,26 @@ clickhouse:
|
|
|
298
301
|
- **Mutation counts are best-effort.** `update_all`/`delete_all` return a pre-mutation `SELECT count()` — ClickHouse reports no affected-row counts.
|
|
299
302
|
- **Eventual merges.** `ReplacingMergeTree` deduplicates at merge time; read with `.final` when you need collapsed rows.
|
|
300
303
|
- **Failover never replays a request.** With `hosts:`, only connect-phase failures (refused, unreachable, open timeout) move to the next replica — those cannot have executed anything. A mid-flight failure (read timeout, reset) raises, because the statement may already have run.
|
|
304
|
+
- **Read-only is server-enforced.** `read_only: true` stamps `readonly=2` on every request; the server's refusal (code 164) raises `ActiveRecord::ReadOnlyError` — the same class Rails' `while_preventing_writes` uses — including for server-configured readonly users. Grant refusals (code 497) raise `ClickHouse::AccessDenied`.
|
|
305
|
+
- **`matches` renders `ILIKE`.** ClickHouse `LIKE` is case-sensitive (unlike MySQL), so Arel's case-insensitive `matches`/`does_not_match` use native `ILIKE`.
|
|
306
|
+
- **No row locks.** Reads are isolated snapshots of parts, so `FOR UPDATE` renders as nothing and shared `lock`/`with_lock` code runs instead of dying. Optimistic locking via `lock_version` works end-to-end.
|
|
307
|
+
|
|
308
|
+
## Migrating from clickhouse-activerecord
|
|
309
|
+
|
|
310
|
+
This gem registers the same `"clickhouse"` adapter name as [clickhouse-activerecord](https://github.com/PNixx/clickhouse-activerecord) and accepts its `database.yml` shape — swap the gem and the config carries over. The two gems cannot be loaded at once.
|
|
311
|
+
|
|
312
|
+
Schema-version bookkeeping does not carry over. The incumbent records applied versions in a `(version, active, ver)` ReplacingMergeTree with `active = 0` tombstones; this adapter uses Rails' plain `schema_migrations` table and does not read that shape, so a sink migrated by the incumbent reports zero applied versions.
|
|
313
|
+
|
|
314
|
+
Never re-run migrations over a live sink to fix that — replaying DDL over final-state tables can corrupt data (enum narrowing, for one). Adopt the version state once instead:
|
|
315
|
+
|
|
316
|
+
```ruby
|
|
317
|
+
versions = connection.select_values("SELECT version FROM schema_migrations FINAL WHERE active = 1")
|
|
318
|
+
connection.execute("RENAME TABLE schema_migrations TO schema_migrations_legacy")
|
|
319
|
+
pool.schema_migration.create_table
|
|
320
|
+
versions.each { |v| pool.schema_migration.create_version(v) }
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Pending migrations run normally afterwards. To roll back the adoption, drop the new `schema_migrations` table and rename the legacy one back.
|
|
301
324
|
|
|
302
325
|
## Development
|
|
303
326
|
|
|
@@ -317,7 +340,7 @@ RAILS_SOURCE=edge bundle install
|
|
|
317
340
|
RAILS_SOURCE=edge bundle exec rspec
|
|
318
341
|
```
|
|
319
342
|
|
|
320
|
-
The suite includes a Rails compatibility harness that runs vendored upstream Active Record test suites (~
|
|
343
|
+
The suite includes a Rails compatibility harness that runs vendored upstream Active Record test suites (~5,600 tests — 5,558 runs, 447 skips, each skip documenting the dialect truth behind it) against the adapter. See `spec/rails_compat/`.
|
|
321
344
|
|
|
322
345
|
## History
|
|
323
346
|
|
|
@@ -295,24 +295,45 @@ module ActiveRecord
|
|
|
295
295
|
end
|
|
296
296
|
end
|
|
297
297
|
|
|
298
|
-
|
|
298
|
+
COMMENT = %r{/\*(?:[^*]|\*(?!/))*\*/}m
|
|
299
299
|
INSERT_STATEMENT = /\A\s*INSERT\b/i
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
#
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
300
|
+
# Data outside strings/comments: any non-space run, but a `/` only when it doesn't open a comment.
|
|
301
|
+
DATA_RUN = %r{(?:[^\s'`/]|/(?!\*))+}
|
|
302
|
+
private_constant :COMMENT, :INSERT_STATEMENT, :DATA_RUN
|
|
303
|
+
|
|
304
|
+
# ClickHouse reads everything after VALUES with the Values input format, which rejects
|
|
305
|
+
# trailing comments (probed 2026-07-13), so sqlcommenter tags Rails' QueryLogs appends
|
|
306
|
+
# are hoisted to the front of INSERTs. Scans string-aware — a /* inside a value (a request
|
|
307
|
+
# path like "/*") is opaque and never taken for a comment, so the VALUES list stays intact.
|
|
308
|
+
def hoist_trailing_comments(sql) # rubocop:disable Metrics/MethodLength
|
|
309
|
+
# ClickHouse strings are byte sequences; SQL carrying invalid UTF-8 must reach the server
|
|
310
|
+
# rather than die in the scan, so it drops to binary (the patterns are pure ASCII).
|
|
309
311
|
sql = sql.b unless sql.valid_encoding?
|
|
310
312
|
return sql unless INSERT_STATEMENT.match?(sql)
|
|
311
313
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
+
scanner = StringScanner.new(sql)
|
|
315
|
+
data_end = 0
|
|
316
|
+
trailing = []
|
|
317
|
+
until scanner.eos?
|
|
318
|
+
next if scanner.skip(/\s+/)
|
|
319
|
+
|
|
320
|
+
if (comment = scanner.scan(COMMENT))
|
|
321
|
+
trailing << comment
|
|
322
|
+
else
|
|
323
|
+
consume_data_token(scanner)
|
|
324
|
+
trailing.clear
|
|
325
|
+
data_end = scanner.pos
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
return sql if trailing.empty?
|
|
329
|
+
|
|
330
|
+
"#{trailing.join(" ")} #{sql[0...data_end]}"
|
|
331
|
+
end
|
|
314
332
|
|
|
315
|
-
|
|
333
|
+
# One opaque data token: a string literal or backtick identifier (skipped whole, so a /*
|
|
334
|
+
# inside never reads as a comment), an ordinary data run, or a lone char that starts none.
|
|
335
|
+
def consume_data_token(scanner)
|
|
336
|
+
scanner.scan(STRING_LITERAL) || scanner.scan(BACKTICK_IDENTIFIER) || scanner.scan(DATA_RUN) || scanner.getch
|
|
316
337
|
end
|
|
317
338
|
|
|
318
339
|
# Rails main (8.2) funnels execution through a QueryIntent object; released
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-clickhouse-adapter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ikraam Ghoor
|
|
@@ -76,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
76
76
|
- !ruby/object:Gem::Version
|
|
77
77
|
version: '0'
|
|
78
78
|
requirements: []
|
|
79
|
-
rubygems_version: 4.0.
|
|
79
|
+
rubygems_version: 4.0.3
|
|
80
80
|
specification_version: 4
|
|
81
81
|
summary: ClickHouse database adapter for Active Record
|
|
82
82
|
test_files: []
|