@@ -21,21 +21,39 @@ class ClickhouseCLIClientTransport implements TransportInterface
21
21
*/
22
22
protected $ executablePath ;
23
23
24
+ /**
25
+ * Path to executable cat command
26
+ *
27
+ * @var string
28
+ */
29
+ protected $ catExecutablePath ;
30
+
24
31
/**
25
32
* Last execute query
26
33
*
27
34
* @var string
28
35
*/
29
36
protected $ lastQuery = '' ;
30
37
38
+ /**
39
+ * Use special ccat command to cat files into one without ARG_MAX limits
40
+ *
41
+ * @var bool
42
+ */
43
+ protected $ useCcat ;
44
+
31
45
/**
32
46
* ClickhouseCLIClientTransport constructor.
33
47
*
34
48
* @param string|null $executablePath
49
+ * @param string|null $catExecutablePath
50
+ * @param bool $useCcat
35
51
*/
36
- public function __construct (string $ executablePath = null )
52
+ public function __construct (string $ executablePath = null , string $ catExecutablePath = null , bool $ useCcat = false )
37
53
{
38
54
$ this ->setExecutablePath ($ executablePath );
55
+ $ this ->setCatExecutablePath ($ catExecutablePath );
56
+ $ this ->useCcat = $ useCcat ;
39
57
}
40
58
41
59
/**
@@ -52,6 +70,20 @@ protected function setExecutablePath(string $executablePath = null)
52
70
$ this ->executablePath = $ executablePath ;
53
71
}
54
72
73
+ /**
74
+ * Set path to cat executable.
75
+ *
76
+ * @param string|null $catExecutablePath
77
+ */
78
+ protected function setCatExecutablePath (string $ catExecutablePath = null )
79
+ {
80
+ if (is_null ($ catExecutablePath )) {
81
+ $ catExecutablePath = 'cat ' ;
82
+ }
83
+
84
+ $ this ->catExecutablePath = $ catExecutablePath ;
85
+ }
86
+
55
87
/**
56
88
* Sends query to given $server.
57
89
*
@@ -99,6 +131,57 @@ public function sendAsyncFilesWithQuery(Server $server, string $query, array $fi
99
131
return $ result ;
100
132
}
101
133
134
+ /**
135
+ * Sends files as one block of data.
136
+ *
137
+ * @param \Tinderbox\Clickhouse\Server $server
138
+ * @param string $query
139
+ * @param array $files
140
+ * @param array $settings
141
+ *
142
+ * @return bool
143
+ * @throws \Throwable
144
+ */
145
+ public function sendFilesAsOneWithQuery (Server $ server , string $ query , array $ files , array $ settings = []): bool
146
+ {
147
+ $ this ->setLastQuery ($ query );
148
+
149
+ /*
150
+ * We will put the list of files into tmp file and then we will use command:
151
+ *
152
+ * (IFS=$'\n'; cat $(< tmp-file))
153
+ *
154
+ * to iterate through file and cat each file into stdout
155
+ *
156
+ * ccat is used to ignore ARG_MAX constant when trying to push too many files
157
+ */
158
+ if ($ this ->useCcat ) {
159
+ $ files = implode (PHP_EOL , $ files );
160
+ } else {
161
+ $ files = implode (PHP_EOL , array_map (function (string $ file ) {
162
+ return 'cat ' .$ file ;
163
+ }, $ files ));
164
+ }
165
+
166
+ $ fileName = $ this ->writeTemporaryFile ($ files );
167
+
168
+ $ command = $ this ->buildCommandForWriteFilesAsOne ($ server , $ query , $ fileName , $ settings );
169
+
170
+ try {
171
+ $ this ->executeCommand ($ command );
172
+
173
+ if (!is_null ($ fileName )) {
174
+ $ this ->removeTemporaryFile ($ fileName );
175
+ }
176
+ } catch (\Throwable $ e ) {
177
+ $ this ->removeTemporaryFile ($ fileName );
178
+
179
+ throw $ e ;
180
+ }
181
+
182
+ return true ;
183
+ }
184
+
102
185
/**
103
186
* Executes SELECT queries and returns result.
104
187
*
@@ -120,13 +203,13 @@ public function get(Server $server, string $query, $tables = null): Result
120
203
$ response = $ this ->executeCommand ($ command );
121
204
122
205
if (!is_null ($ file )) {
123
- $ this ->removeQueryFile ($ file );
206
+ $ this ->removeTemporaryFile ($ file );
124
207
}
125
208
126
209
return $ this ->assembleResult ($ response );
127
210
} catch (\Throwable $ e ) {
128
211
if (!is_null ($ file )) {
129
- $ this ->removeQueryFile ($ file );
212
+ $ this ->removeTemporaryFile ($ file );
130
213
}
131
214
132
215
throw $ e ;
@@ -165,7 +248,7 @@ public function getAsync(Server $server, array $queries, int $concurrency = 5):
165
248
*
166
249
* @return string
167
250
*/
168
- protected function writeQueryInFile (string $ query ) : string
251
+ protected function writeTemporaryFile (string $ query ) : string
169
252
{
170
253
$ tmpDir = sys_get_temp_dir ();
171
254
$ fileName = tempnam ($ tmpDir , 'clickhouse_client ' );
@@ -182,7 +265,7 @@ protected function writeQueryInFile(string $query) : string
182
265
*
183
266
* @param string $fileName
184
267
*/
185
- protected function removeQueryFile (string $ fileName )
268
+ protected function removeTemporaryFile (string $ fileName )
186
269
{
187
270
unlink ($ fileName );
188
271
}
@@ -193,17 +276,18 @@ protected function removeQueryFile(string $fileName)
193
276
* @param \Tinderbox\Clickhouse\Server $server
194
277
* @param string $query
195
278
* @param string|null $file
279
+ * @param array $settings
196
280
*
197
281
* @return string
198
282
*/
199
- protected function buildCommandForWrite (Server $ server , string $ query , string $ file = null ) : string
283
+ protected function buildCommandForWrite (Server $ server , string $ query , string $ file = null , array $ settings = [] ) : string
200
284
{
201
285
$ query = escapeshellarg ($ query );
202
286
203
287
$ command = [];
204
288
205
289
if (!is_null ($ file )) {
206
- $ command [] = " cat " .$ file .' | ' ;
290
+ $ command [] = $ this -> catExecutablePath . ' ' .$ file .' | ' ;
207
291
}
208
292
209
293
$ command = array_merge ($ command , [
@@ -214,6 +298,47 @@ protected function buildCommandForWrite(Server $server, string $query, string $f
214
298
"--query= {$ query }"
215
299
]);
216
300
301
+ foreach ($ settings as $ key => $ value ) {
302
+ $ command [] = '-- ' .$ key .'= ' .escapeshellarg ($ value );
303
+ }
304
+
305
+ return implode (' ' , $ command );
306
+ }
307
+
308
+ /**
309
+ * Builds command for write
310
+ *
311
+ * @param \Tinderbox\Clickhouse\Server $server
312
+ * @param string $query
313
+ * @param string|null $file
314
+ * @param array $settings
315
+ *
316
+ * @return string
317
+ */
318
+ protected function buildCommandForWriteFilesAsOne (Server $ server , string $ query , string $ file , array $ settings = []) : string
319
+ {
320
+ $ query = escapeshellarg ($ query );
321
+
322
+ $ command = [];
323
+
324
+ if ($ this ->useCcat ) {
325
+ $ command [] = $ this ->catExecutablePath .' ' .$ file .' | ' ;
326
+ } else {
327
+ $ command [] = '$( ' .$ this ->catExecutablePath .' ' .$ file .') | ' ;
328
+ }
329
+
330
+ $ command = array_merge ($ command , [
331
+ $ this ->executablePath ,
332
+ "--host=' {$ server ->getHost ()}' " ,
333
+ "--port=' {$ server ->getPort ()}' " ,
334
+ "--database=' {$ server ->getDatabase ()}' " ,
335
+ "--query= {$ query }" ,
336
+ ]);
337
+
338
+ foreach ($ settings as $ key => $ value ) {
339
+ $ command [] = '-- ' .$ key .'= ' .escapeshellarg ($ value );
340
+ }
341
+
217
342
return implode (' ' , $ command );
218
343
}
219
344
@@ -223,15 +348,16 @@ protected function buildCommandForWrite(Server $server, string $query, string $f
223
348
* @param \Tinderbox\Clickhouse\Server $server
224
349
* @param string $query
225
350
* @param null $tables
351
+ * @param array $settings
226
352
*
227
353
* @return array
228
354
*/
229
- protected function buildCommandForRead (Server $ server , string $ query , $ tables = null ) : array
355
+ protected function buildCommandForRead (Server $ server , string $ query , $ tables = null , array $ settings = [] ) : array
230
356
{
231
- $ fileName = $ this ->writeQueryInFile ($ query );
357
+ $ fileName = $ this ->writeTemporaryFile ($ query );
232
358
233
359
$ command = [
234
- " cat {$ fileName } | " ,
360
+ $ this -> catExecutablePath . " {$ fileName } | " ,
235
361
$ this ->executablePath ,
236
362
"--host=' {$ server ->getHost ()}' " ,
237
363
"--port=' {$ server ->getPort ()}' " ,
@@ -249,6 +375,10 @@ protected function buildCommandForRead(Server $server, string $query, $tables =
249
375
}
250
376
}
251
377
378
+ foreach ($ settings as $ key => $ value ) {
379
+ $ command [] = '-- ' .$ key .'= ' .escapeshellarg ($ value );
380
+ }
381
+
252
382
return [implode (' ' , $ command ), $ fileName ];
253
383
}
254
384
0 commit comments