Skip to content

Added Support for Fetch Modes#4

Open
iliaal wants to merge 172 commits into
SeasX:masterfrom
iliaal:master
Open

Added Support for Fetch Modes#4
iliaal wants to merge 172 commits into
SeasX:masterfrom
iliaal:master

Conversation

@iliaal

@iliaal iliaal commented Dec 15, 2019

Copy link
Copy Markdown

Couple of feature enhancements to simplify data retrieval

  1. Added ability to fetch dates as strings (date as Y-m-d and datetime as Y-m-d H:i:s similar to how it is returned by HTTP client) via DATE_AS_STRINGS mode
  2. Added ability to fetch single value via FETCH_ONE mode
  3. Added ability to retrieve all volumes from single column as an basic array of values via FETCH_COLUMN mode
  4. Added ability to fetch results as an associated array (key-value-pair) via FETCH_KEY_PAIR mode using col1 as index and col2 as value.

Also added SeasClickException class so that exceptions thrown are specific to extension as opposed to using generic Exception class.

@wujunze wujunze requested review from Neeke and aiwhj December 16, 2019 04:32

@769344359 769344359 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why remove shutdown callback ?

Comment thread SeasClick.cpp
SEASCLICK_RES_NAME,
SeasClick_functions,
PHP_MINIT(SeasClick),
PHP_MSHUTDOWN(SeasClick),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why remove shutdown callback?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't do anything, so recommended approach is to not invlude PHP_MSHUT / PHP_MINIT unless they do something

Comment thread SeasClick.cpp Outdated
};

#define REGISTER_SC_CLASS_CONST_LONG(const_name, value) \
zend_declare_class_constant_long(SeasClick_ce, const_name, sizeof(const_name)-1, (zend_long)value);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do some compatibility tests, such as zend_long type does not support PHP 5 version

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread SeasClick.cpp Outdated
convertToZval(col2, block[1], row, "", 0, fetch_mode|SC_FETCH_ONE);

if (Z_TYPE_P(col1) == IS_LONG) {
zend_hash_index_update(Z_ARRVAL_P(return_value), Z_LVAL_P(col1), col2);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compatibility

#define zend_hash_index_update(ht, h, pData, nDataSize, pDest) _zend_hash_index_update_or_next_insert(ht, h, pData, nDataSize, pDest, HASH_UPDATE ZEND_FILE_LINE_CC)

This is the definition of PHP 5 version

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@iliaal iliaal requested a review from 769344359 April 6, 2020 12:02
@iliaal iliaal requested a review from aiwhj April 6, 2020 12:47
@guba-odudkin

Copy link
Copy Markdown

@769344359 @Neeke Any chance to get it merged?

@Rock-520

Copy link
Copy Markdown
Member

@769344359 @Neeke Any chance to get it merged?

it will have been merged when i finsh unit tests!

@Rock-520

Rock-520 commented Mar 7, 2023

Copy link
Copy Markdown
Member

image

@guba-odudkin could you please help me to fix the conflicts ? it change a lot of file, i can not merge it .

Resyncs this PR with SeasX/SeasClick master after five years of drift.
Brings in upstream's PHP 8 support, the writeStart/write/writeEnd
block-insert methods, the GCC 10 compile fix, and the package.xml
bumps. Keeps the PR's fetch-mode features (FETCH_ONE, FETCH_KEY_PAIR,
FETCH_COLUMN, DATE_AS_STRINGS), the SeasClickException class, and the
timeout/retry properties.

Conflict resolutions:

- SeasClick.cpp: merged the fetch-mode select() with upstream's write*
  block-insert methods. Standardized arg-info names on SeasClick_*
  (upstream's master shipped a SeasCilck_ typo), and routed every
  catch through sc_zend_throw_exception_tsrmls_cc against
  SeasClickException_ce so PHP 7 and PHP 8 builds both link.
- client.cpp: kept upstream's split InsertData / InsertDataEnd API.
  The PR's PHP-side writeStart/write/writeEnd already calls them in
  that order, so the combined InsertData from the lib upgrade was
  wrong.
- php7_wrapper.h: changed the ZEND_ACC_DTOR fallback from 0x4000 to 0.
  PHP 8 reused bit 14 for ZEND_ACC_VARIADIC, and the old fallback was
  crashing zend_register_functions on extension load.
- nullable.cpp: dropped the duplicate <stdexcept> include the
  auto-merge introduced.

Verified against PHP 7.1, 7.2, 7.4, 8.3 (debug+ASAN), 8.4 (debug+ASAN
and release), and 8.5 (debug+ASAN). Extension loads, all nine methods
and four fetch-mode constants register, and SeasClickException extends
Exception.
@iliaal

iliaal commented Apr 25, 2026

Copy link
Copy Markdown
Author

@Rock-520 Not sure if this repo is still alive, but PR updated and merges cleanly

iliaal added 2 commits April 25, 2026 11:56
The PR's package.xml has declared <min>7.0.0</min> for years and the
upstream code path stopped compiling on PHP 5 several releases ago.
Carrying the dual-PHP scaffolding only obscures the real PHP 7 vs
PHP 8 distinctions, so strip it.

- php7_wrapper.h: deleted the entire PHP_MAJOR_VERSION < 7 branch
  (PHP 5 alternates for hash/zval/exception calls). Removed TSRMLS_CC
  from PHP 7 wrapper bodies (the macro is empty on PHP 7+ and absent
  on PHP 8). Removed the ZEND_ACC_DTOR fallback now that the only
  callsite is gone.
- SeasClick.cpp: collapsed the PHP_VERSION_ID >= 70000 / <= 70000 /
  < 70000 conditionals around class registration, property
  declarations, and write() argument fetching. Stripped TSRMLS_CC
  from every zend_parse_parameters call. Dropped ZEND_ACC_DTOR from
  the __destruct method entry; PHP 7+ recognizes destructors by name.

Net: -155 lines.

Verified against PHP 7.4 (Docker), PHP 8.3 (debug+ASAN), and PHP 8.4
(release). Extension loads, all nine methods and four constants
register, SeasClickException extends Exception.
Replaces the artpaul-fork v1.x vendored library with the official
ClickHouse/clickhouse-cpp v2.6.1. Brings in DateTime64, LowCardinality,
Map, Geo, Time and Time64 column types from upstream, modern
ClientOptions, optional ZSTD compression on the wire, and a current
contrib (cityhash, lz4, abseil-int128, zstd).

PHP-side surface changes:

- DateTime64(N) now accepts epoch-seconds (int) or "Y-m-d H:i:s"
  strings on insert, and reads back either as int or as "Y-m-d
  H:i:s.ffffff" when DATE_AS_STRINGS is set.
- LowCardinality(String) and LowCardinality(FixedString(N)) round-trip
  through the dictionary column. Read returns plain strings.
- Map(K, V) columns can be created via factory; per-row read/write
  through the new ColumnMapT API is left as TODO.

Library / build mechanics:

- Bumped to C++17 (clickhouse-cpp v2 requires it).
- Vendored contrib/zstd in full (compress, decompress, common). zstd
  is a hard dep of the new compressed.cpp -- there is no compile-time
  gate. Built with -DZSTD_DISABLE_ASM since we don't compile the .S
  optimization file.
- Reworked config.m4 source list (~80 sources now) and added include
  paths for the new contrib trees.
- writeStart/write/writeEnd were rewired from the v1.x
  InsertQuery/InsertData/InsertDataEnd API to v2.x
  BeginInsert/SendInsertBlock/EndInsert.
- ClientOptions field renames: SetSocketReceiveTimeout ->
  SetConnectionRecvTimeout, SetSocketConnectTimeout ->
  SetConnectionConnectTimeout.
- Fixed two ZPP / arg-info mismatches PHP 8.3 caught: select() and
  execute() now declare their first argument as the only required
  one. The previous values were inherited from the original PR and
  worked on PHP 8.4 by accident.

Build artifacts now ignored via .gitignore (Makefile, configure,
config.*, autom4te.cache/, build/, modules/, *.dep, *.loT, etc.).

Verified end-to-end against ClickHouse 24.8.14 (docker
clickhouse/clickhouse-server) on PHP 7.4 (docker), PHP 8.3
(debug+ASAN), and PHP 8.4 (release): connect, ping, execute, insert,
select with FETCH_ONE / FETCH_COLUMN / FETCH_KEY_PAIR / DATE_AS_STRINGS,
writeStart/write/writeEnd block insert, DateTime64(3)/(6) round-trip,
LowCardinality(String) round-trip with correct cardinality.

PHP 8.3 ASAN reports indirect leaks (~17KB) inside Client::Impl
construction (NonSecureSocketFactory and friends) that are vendored-
library-internal and only surface at process exit. Functional path is
clean -- no SEGV, no use-after-free.

README updated: dependency list now reflects v2.x types, install
section mentions the C++17 / vendored-contrib model, and added a
"Testing against a local ClickHouse server" section pointing at the
clickhouse/clickhouse-server image with a one-liner docker run.
iliaal added 30 commits June 10, 2026 14:18
Both deliberately switch to de_DE to prove the extension formats floats
locale-independently; they only ever ran on CI when the runner shipped
de_DE (it doesn't), so two latent 7.4 issues stayed hidden until the
local 7.4 lane gained the locale.

064: the write path is correct (the {x:Float64} round-trip reaches the
server as "1.5" and succeeds), but the assertion echoed the returned
double. PHP < 8.0 renders a float through LC_NUMERIC ("1,5" under de_DE);
PHP 8.0 made float-to-string locale-independent. Compare the value
numerically instead so the test verifies the wire format, not the
engine's echo behavior.

070: a PHP array literal [1.5 => "a"] coerces the float key to int
(silent < 8.1, deprecated after), so the keys never reached the Float64
read decoder the test targets -- the EXPECTF never matched reality, which
is why it skipped on >= 8.1 and silently mis-ran below. Build the map
server-side via map(1.5,'a',0.1,'b'), drop the version skip, and match
the locale-independent string key php_gcvt emits (0.10000000000000001).

Verified on 7.4 and 8.4 (identical keys); full 8.4 suite 143 passed,
0 failed.
- Update vendored tree to 2.6.2
- Mark memcpy guard in string.cpp as obsoleted (upstreamed)
- Re-apply/adapt remaining 5 local patches (BeginInsert query_id, cityhash guard, public Query overloads for BeginInsert+SelectWithExternalData, early insert state clear in ResetConnection)
- Add new columns bool.cpp/json.cpp to config.m4 + config.w32
- Fix Map(Float) key lifetime: release ref on temporary map_zv after add, use std::string for float key strings instead of stack kbuf
- Clear per-Query callbacks after Select to avoid closures holding refs to do_select_into stack
- Add missing main/snprintf.h include for php_gcvt prototype
- 070.phpt now strict (no trailing %a); test passes cleanly under ASAN build
- Add minimal repro tests/070_minimal.php for the locale+Map(Float) case
- Update docs and changelog for the bump
Removed the erroneous Z_DELREF_P after add_next_index_zval/sc_add_assoc_zval_ex
for the map_zv (and the comment claiming the add does an inc).

This was causing heap-use-after-free on the Map array (allocated at array_init
in convertToZval) under ASAN when the result row / foreach / json_encode etc.
later destroyed it. The add takes ownership of the refcount=1 from array_init;
ZVAL_UNDEF is sufficient to detach the SC_MAKE pointer temp (consistent with
emitNestedZval and other complex column paths in the same file).

The std::string float-key copies (in the kkind==2 branches) and the 7
post-Select On* clears remain.

Addresses the sanitizer (and ZTS) crashes on 019.phpt (and related Map tests)
that made the 2.6.2 bump CI red. 086.phpt paths were inspected and untouched
by this change.
…n to 2.6.2

The 5 local patches re-applied against vendored clickhouse-cpp v2.6.2 are
now kept as applyable .patch files under patches/upstream/, so future lib
bumps can git-apply them instead of hand-reconstructing from LOCAL_PATCHES.md
prose. Bump .upstream/clickhouse-cpp.yml pin 2.6.1 -> 2.6.2 to match what is
vendored (and what upstream ships); the empty string_view memcpy UB guard is
now upstream and no longer carried locally.
clickhouse-cpp 2.6.2's VariantIndex trait built a std::variant in a
constexpr non-type template argument, which MSVC 14.29 (VS2019, the PHP 8.3
Windows toolset) rejects with C2440. VS2022 (8.4/8.5) and GCC/Clang accept
it, so the break only hit the 8.3 Windows lane -- and only in
release-windows.yml, which runs after the tag. 0.8.6 shipped with no PHP 8.3
Windows DLLs as a result.

Compute the index with a std::is_same_v fold instead (no std::variant
constructed); identical result, portable across toolsets. Drop the now-unused
VariantIndexTag. Recorded as local patch 0006 + LOCAL_PATCHES.md entry.

Expand the pre-tag Windows gate (tests.yml) from 8.4 to 8.3/8.4/8.5 so a
vs16-only break blocks the release instead of surfacing post-tag.
…to-discard

clickhouse_free_obj() called ResetConnection() to "discard" an orphaned
streaming insert (writeStart/write with no writeEnd). That goal is
unreachable. ClickHouse inserts are not transactional, so streamed blocks
may already be written to parts, and dropping the connection only discards
inserts small enough to sit in the server squash buffer, making it
size-dependent and silently partial. It also opened a fresh connection plus
handshake at destruction just to drop it, and left a latent bug: when
connect() threw, state_ stayed Inserting and ~Client committed the insert
anyway on the old socket.

Now free_obj just deletes the client and lets ~Client wind the insert down
on the existing wire. The ResetConnection calls in the write()/insert()
error paths stay; there they recover a wedged inserting_ handle rather than
roll back, with the framing corrected in comments.

Behavior change: an abandoned streaming insert now finalizes whatever the
server accepted instead of attempting to discard it. Updated 091.phpt and
CHANGELOG accordingly.
…ion discard flag

write()'s error path keyed recovery off the session-level insert_blocks_sent
flag: once any block had been sent in the session, a later write() that threw
during row conversion (a healthy wire) went through ResetConnection() to
"discard" the insert. Same illusory rollback the teardown path had: ClickHouse
is not transactional, so earlier blocks may already be persisted, and the
discard only worked for inserts small enough to sit in the server squash
buffer.

Distinguish a dirty wire from a healthy one with a per-call block_send_started
local, set immediately before SendInsertBlock. A throw before that point (row
conversion) leaves the wire healthy, so the catch finalizes with EndInsert()
to match the teardown policy, falling back to ResetConnection() only if the
finalize itself throws. A throw from SendInsertBlock leaves the wire dirty and
still ResetConnection()s for handle recovery. The session-level
insert_blocks_sent flag is now unused and removed.

Updated 089.phpt to assert the finalize behavior and refreshed the stale
comment in 142.phpt.
Wire the vendored clickhouse-cpp ColumnJSON through the PHP boundary on
both the read and write paths.

insert() auto-detects by PHP type: an array or object is json_encode'd, a
string is stored as raw JSON text (validated client-side so malformed
input fails fast instead of mid-stream), and null becomes the empty
object {}, matching the lib's Nullable(JSON) convention.

select() returns the raw JSON string by default. The new
ClickHouse::JSON_AS_ARRAY and JSON_AS_OBJECT fetch flags decode it to a
nested associative array or nested stdClass. Reads require
output_format_native_write_json_as_string=1 on the session; without it
the vendored client throws a protocol error on the column prefix.

selectStream() and selectStreamCallback() gain a trailing fetch_mode
argument, so the bitmask (the new JSON flags and the existing
DATE_AS_STRINGS) now applies on the streaming read paths too.

Tests cover the three read modes, array/object/raw-string insert,
Nullable(JSON), streaming decode, and insert validation, gated behind a
JSON-capability skip helper.
PHP 7.4's php_json_decode takes a non-const char* buffer, so the
read-path decode passing std::string::c_str() (const char*) failed to
convert there. Pass &str[0] instead, a mutable NUL-terminated pointer
that PHP 8's const parameter accepts too.
Bool now reads as a PHP bool and writes from a bool or int. IPv4 and IPv6
gain write support (both were read-only): IPv4 accepts a dotted-quad
string or an integer matching ClickHouse's toIPv4() convention
(16909060 inserts as 1.2.3.4), IPv6 accepts a dotted/colon string. All
three validate client-side and support Nullable; IPv4/IPv6 null rows
write a masked sentinel because the column's inet_pton path rejects
empty input.

Also correct the README: top-level Tuple inserts work (the "read-only"
note was stale), though Array(Tuple) writes remain unsupported.

Tests cover the round-trips, integer-vs-toIPv4() parity, Nullable
variants, and the bad-input rejection paths.
createColumn(Tuple) now builds an empty ColumnTuple from the field
types instead of throwing, so a Tuple can serve as the element column
of Array(Tuple) on the write path.

The Array insert loop dropped its reusable-child + Clear() pattern in
favor of appending each row's column directly. AppendAsColumn copies
the row data into the array's flat column, so the indirection was
unnecessary, and ColumnTuple::Clear() drops its sub-columns rather than
emptying their rows, which made the old pattern unusable for tuples.
Plain Array(scalar) inserts are unaffected.

Also replace the three remaining em-dashes in README.md with commas or
sentence breaks.
Security:
- Decimal read: bound the server-declared scale at 38 before the in-place
  format. A scale beyond 38 (a Decimal256, or a hostile/MITM server)
  drove memset/memmove past a fixed 64-byte stack buffer on the first
  decoded row of a select(). Mirrors the DateTime64 precision guard.

Correctness:
- strict_zval_* now ZVAL_DEREF their input, so by-reference cells nested
  in Array/Tuple insert rows coerce instead of throwing a misleading
  "array/object/resource cannot be assigned" error.
- ZStrGuard throws when a __toString() left EG(exception) pending, so an
  insert aborts cleanly and surfaces the original exception instead of
  committing a corrupted "" cell.
- DateTime64/Time64 reject a float at precision >= 7 (a double cannot hold
  the tick count exactly) and bound the precision at 9 on insert, closing
  a signed-int64 overflow in the scale loop.
- IPv4 inserts accept an integral float, consistent with integer columns.
- ClickHouseStatement::__construct returns after throwing.

Performance:
- Array(scalar) inserts reuse one element column across rows again; the
  per-row allocation now applies only to Tuple elements, where Clear()
  drops sub-columns. Reverts a regression from Array(Tuple) support.
- JSON insert string validation uses php_json_validate on 8.3+ (no value
  tree materialized); decode-and-discard on older PHP.

Docs: README write-side coercion rules, Bool truthiness caveat, and a
selectStream-buffering note.

Findings from a 5-agent security/quality/perf/standards/correctness
review. tests/149.phpt covers the security and correctness fixes.
…verflow

The ASAN/UBSan lane caught a signed-integer-overflow at the DateTime64
insert: the integer path treats the value as whole seconds and multiplies
by 10^precision, which overflows int64 for an out-of-range input (the new
tests/149.phpt passed ticks instead of seconds and tripped it). The
multiply is now bounded before it runs, and the misleading "integer ticks"
wording is corrected to "whole seconds" (the value is scaled internally,
matching DateTime). Same guard applied to Time64. The test now passes
epoch seconds.
Casting an out-of-range double to int64_t is UB (C11 6.3.1.4p1); on
x86-64 it silently saturates rather than trapping. The sub-precision-7
float path did (int64_t)(d * scale) unguarded, so a large epoch (1e300)
was undefined. A new checked_double_to_int64 helper bounds the value
against [-2^63, 2^63) before the cast and throws otherwise; both the
DateTime64 and Time64 float paths use it. The extension's ASAN job builds
with clang, whose -fsanitize=undefined includes float-cast-overflow, so
tests/149.phpt's out-of-range case gates this class.
…g paths

A PHP reference (IS_REFERENCE) reaching a type/shape branch via Z_TYPE_P
was mishandled in every path that didn't go through the strict_zval_*
helpers (which already deref). The worst cases silently wrote a
placeholder instead of erroring:

- Array(Nullable(Int32)) with a by-ref null stored 0; the null bitmap
  checked Z_TYPE_P without deref while the child build (under the
  allow-null guard) wrote a 0 placeholder. Array(LowCardinality(
  Nullable(String))) stored "" the same way.
- A by-ref settings value serialized to "" (formatScalarParam switched
  without deref), and the server rejected it ("Cannot parse bool from
  string ''").
- A by-ref constructor config value was mishandled: ['compression' =>
  &"zstd"] silently fell back to LZ4; by-ref endpoints was rejected.
  php_array_get_value now derefs the found value, covering every config
  key.

Swept the remaining direct Z_TYPE_P(array_value) sites in insertColumn
and the geo/UUID helpers (Enum, Date, LowCardinality, Map, Int128,
UInt128, Time, Array element, Tuple row + field, Point/Ring/Polygon/
MultiPolygon). tests/150.phpt covers the composite, settings, and config
cases.

Also: config.m4 now validates openssl/ssl.h + ssl/crypto when
--enable-clickhouse-openssl is requested, failing with a clear message
instead of an opaque missing-header compile error (config.w32 already
did the equivalent CHECK_LIB/CHECK_HEADER).

Findings from an external review pass.
…HP 7.4/8.0

tests/150.phpt read the protected compression property via Reflection
without setAccessible(true), which 8.1+ made implicit but 7.4/8.0 still
require. Code path was fine; only the test was version-sensitive.
setAccessible(true) is required on PHP 7.4/8.0 but deprecated in 8.5
(emitting a notice that fails the --EXPECT-- match). A bound closure
reads the protected compression property cleanly across 7.4-8.5.
Closure::bind can't scope to an internal class, and setAccessible is
required on 7.4/8.0 but deprecated in 8.5. Call it only on PHP < 8.1,
where it is needed and not deprecated; 8.1+ reads the protected property
without it. Verified locally before push.
- __construct rejects a re-run on an already-constructed object before
  mutating any property, instead of overwriting host/port/db/user and
  only then throwing (fnd_e76c3771).
- A nested array element in a {placeholder} list is rejected rather than
  stringified to the bogus identifier "Array" (fnd_020ad08b). Stringable
  object elements still work (their __toString is honored).
- selectToStream resumes a short write instead of aborting the export;
  a stream wrapper may accept fewer bytes than requested per call
  (fnd_a0b74d40).
- selectStream / selectStreamCallback mask the fetch_mode to the
  value-shaping flags (DATE_AS_STRINGS / JSON_AS_*) before the per-cell
  convertToZval. A result-shape flag (FETCH_ONE / KEY_PAIR / COLUMN) is
  meaningless per cell and made the iterator emit a scalar, fataling on
  current()'s ?array return type (fnd_eb7a982d).
…_35ca0004)

isExists() validated db/table with the strict identifier rule and rejected
any name that wasn't a bare identifier, so a table legitimately named
`my-table` couldn't be checked. But the values are compared as string
VALUES against system.tables, not interpolated as identifiers, so the
correct treatment is sqlStringLiteral escaping (the same as showTables()):
it admits any name and neutralizes injection by escaping rather than
rejecting. Verified the injection probes return false with no SQL executed.

showCreateTable keeps strict identifier validation deliberately: there the
name is interpolated as an identifier, so the validation is real
defense-in-depth (fnd_272c7f8e left as won't-fix per maintainer).

Updated tests/053.phpt: the isExists probes now assert safe-bool results
instead of validator rejections.
Add the missing entries from the review rounds (construct re-run guard,
nested-array placeholder, selectToStream short-write resume, selectStream
shape-flag masking, isExists string-literal escaping, DateTime64/Time64
overflow and float-cast guards). Merge the duplicate Changed section,
reorder to Added/Changed/Fixed/Security, and rewrite the Unreleased
bullets terse and em-dash-free per the ~/ai NEWS-style rule. Released
sections are left untouched.
Move the full usage reference into a self-contained docs/index.html for
GitHub Pages: per-type read/write matrix, fetch modes, configuration,
the grouped method list, JSON columns, CSV/TSV streaming, placeholders,
settings, and observability. The type matrix is verified against
typesToPhp.cpp and the .phpt suite; code blocks get inline PHP
highlighting with no external dependency.

README drops from 494 to a landing page: intro, a prominent docs link,
fork rationale, install, one quick example, benchmarks, and license.
Correct two stale clickhouse-cpp v2.6.1 references to v2.6.2.
Address validated findings from a multi-agent review
(docs/review/2025-06-21/findings.md):

- Map(UUID, …) keys and Map(…, UUID) values read back as bare 32-char
  hex, matching standalone UUID columns; they previously rendered
  hyphenated. New ClickHouse::UUID_WITH_DASHES fetch flag opts into the
  canonical dashed form across select/selectStream/selectStreamCallback
  (CR-003).
- Time inserts reject values outside the int32 range instead of silently
  truncating (CR-005).
- Decimal inserts reject a non-scalar value with a clear client-side
  exception instead of stringifying it to "Array" and failing
  server-side (CR-007).
- FixedString inserts reject an over-width value client-side (CR-012).
- Drop a dead column_names vector in insertFromStream (CR-008).
- config.w32 fails hard when --enable-clickhouse-openssl is set but
  OpenSSL is missing, matching config.m4 (CR-018).
- Bump PHP_CLICKHOUSE_VERSION to 0.8.9.

Tests 152/153 added (the Decimal/FixedString cases assert the
client-side message so a regression to a server-side error flips them);
052 updated for the raw-hex Map output.
The naming-consistency CI guard (scripts/check-no-seasclick.sh) rejects
SeasClick references outside the documented compat surface; a comment
added in the prior commit named it incidentally. Reword to "historical
default".
str_contains() is PHP 8.0+; 152.phpt fataled on the 7.4 CI lane.
Verified the full suite green on a local 7.4 build (152 pass, 1 skip).
Follow-up hardening from the consolidated audit review.

- Route every server-supplied type-metadata cast (As<...Type>()) in the
  create / insert / read paths through a new type_as_or_throw<> helper, so a
  type whose concrete metadata doesn't match its declared code throws instead
  of dereferencing null. Reachable from a malicious or proxied server.
- Wrap the full __construct config-assembly body in the existing try/catch so
  an allocation failure during option setup surfaces as a ClickHouseException
  rather than escaping the Zend dispatcher and aborting the process.
- Add the ClickHouse::FIXEDSTRING_BINARY fetch flag: returns FixedString
  values at their full declared width (trailing NULs preserved) for binary
  payloads, instead of the default trailing-NUL trim. Covers the standalone
  and LowCardinality(FixedString) read paths. Pinned by tests/154.phpt.
- emitVerbose only swallows an exception raised by encoding the trace payload;
  a query exception already pending when the trace fires is preserved.
- Minor: generic SKIPIF messages (no host:port), drop an unused parameter,
  fix an inaccurate 2^64 comment.

Remove the now-consolidated docs/review/2025-06-21/findings.md.
The linux job's "Rebuild with TLS" step only compiled the SSL build path and
never ran a TLS test (CR-019); tests/027.phpt therefore never executed in CI
(CR-026) — the plaintext services: container has no 9440, and GitHub service
containers start before any step runs, so they can't mount a step-generated
cert.

Run the TLS server manually in that step instead: generate a self-signed cert,
start clickhouse-server with a committed tls.xml drop-in (tcp_port_secure 9440,
verificationMode none), rebuild the extension with --enable-clickhouse-openssl,
wait on a plaintext HTTP readiness probe (host 8124), then run 027 against 9440.

run-tests.php exits 0 on all-skip, so the step parses the summary and fails
unless 027 actually ran (skipped=0) and passed (passed=1) — a regression that
drops TLS support or breaks the handshake now blocks CI instead of skipping
silently.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants