Coverage Report

Created: 2025-12-03 08:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/elastic/ogrelasticdatasource.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  Elasticsearch Translator
4
 * Purpose:
5
 * Author:
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2011, Adam Estrada
9
 * Copyright (c) 2012, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "ogr_elastic.h"
15
#include "cpl_conv.h"
16
#include "cpl_string.h"
17
#include "cpl_csv.h"
18
#include "cpl_http.h"
19
#include "ogrlibjsonutils.h"
20
#include "ogr_swq.h"
21
22
/************************************************************************/
23
/*                        OGRElasticDataSource()                        */
24
/************************************************************************/
25
26
OGRElasticDataSource::OGRElasticDataSource()
27
31
    : m_pszName(nullptr), m_bOverwrite(false), m_nBulkUpload(0),
28
31
      m_pszWriteMap(nullptr), m_pszMapping(nullptr), m_nBatchSize(100),
29
31
      m_nFeatureCountToEstablishFeatureDefn(100), m_bJSonField(false),
30
31
      m_bFlattenNestedAttributes(true)
31
31
{
32
31
    const char *pszWriteMapIn = CPLGetConfigOption("ES_WRITEMAP", nullptr);
33
31
    if (pszWriteMapIn != nullptr)
34
0
    {
35
0
        m_pszWriteMap = CPLStrdup(pszWriteMapIn);
36
0
    }
37
31
}
38
39
/************************************************************************/
40
/*                       ~OGRElasticDataSource()                        */
41
/************************************************************************/
42
43
OGRElasticDataSource::~OGRElasticDataSource()
44
31
{
45
31
    m_apoLayers.clear();
46
31
    CPLFree(m_pszName);
47
31
    CPLFree(m_pszMapping);
48
31
    CPLFree(m_pszWriteMap);
49
31
}
50
51
/************************************************************************/
52
/*                           TestCapability()                           */
53
/************************************************************************/
54
55
int OGRElasticDataSource::TestCapability(const char *pszCap) const
56
0
{
57
0
    if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
58
0
        EQUAL(pszCap, ODsCCreateGeomFieldAfterCreateLayer))
59
0
    {
60
0
        return GetAccess() == GA_Update;
61
0
    }
62
63
0
    return FALSE;
64
0
}
65
66
/************************************************************************/
67
/*                             GetIndexList()                           */
68
/************************************************************************/
69
70
std::vector<std::string>
71
OGRElasticDataSource::GetIndexList(const char *pszQueriedIndexName)
72
0
{
73
0
    std::vector<std::string> aosList;
74
0
    std::string osURL(m_osURL);
75
0
    osURL += "/_cat/indices";
76
0
    if (pszQueriedIndexName)
77
0
    {
78
0
        osURL += '/';
79
0
        osURL += pszQueriedIndexName;
80
0
    }
81
0
    osURL += "?h=i";
82
0
    CPLHTTPResult *psResult = HTTPFetch(osURL.c_str(), nullptr);
83
0
    if (psResult == nullptr || psResult->pszErrBuf != nullptr ||
84
0
        psResult->pabyData == nullptr)
85
0
    {
86
0
        CPLHTTPDestroyResult(psResult);
87
0
        return aosList;
88
0
    }
89
90
0
    char *pszCur = (char *)psResult->pabyData;
91
0
    char *pszNextEOL = strchr(pszCur, '\n');
92
0
    while (pszNextEOL && pszNextEOL > pszCur)
93
0
    {
94
0
        *pszNextEOL = '\0';
95
96
0
        char *pszBeforeEOL = pszNextEOL - 1;
97
0
        while (*pszBeforeEOL == ' ')
98
0
        {
99
0
            *pszBeforeEOL = '\0';
100
0
            pszBeforeEOL--;
101
0
        }
102
103
0
        const char *pszIndexName = pszCur;
104
105
0
        pszCur = pszNextEOL + 1;
106
0
        pszNextEOL = strchr(pszCur, '\n');
107
108
0
        if (STARTS_WITH(pszIndexName, ".security") ||
109
0
            STARTS_WITH(pszIndexName, ".monitoring") ||
110
0
            STARTS_WITH(pszIndexName, ".geoip_databases"))
111
0
        {
112
0
            continue;
113
0
        }
114
115
0
        aosList.push_back(pszIndexName);
116
0
    }
117
0
    CPLHTTPDestroyResult(psResult);
118
119
0
    return aosList;
120
0
}
121
122
/************************************************************************/
123
/*                             GetLayerCount()                          */
124
/************************************************************************/
125
126
int OGRElasticDataSource::GetLayerCount() const
127
0
{
128
0
    if (m_bAllLayersListed)
129
0
    {
130
0
        if (m_poAggregationLayer)
131
0
            return 1;
132
0
        return static_cast<int>(m_apoLayers.size());
133
0
    }
134
0
    m_bAllLayersListed = true;
135
136
0
    const auto aosList =
137
0
        const_cast<OGRElasticDataSource *>(this)->GetIndexList(nullptr);
138
0
    for (const std::string &osIndexName : aosList)
139
0
    {
140
0
        const_cast<OGRElasticDataSource *>(this)->FetchMapping(
141
0
            osIndexName.c_str());
142
0
    }
143
144
0
    return static_cast<int>(m_apoLayers.size());
145
0
}
146
147
/************************************************************************/
148
/*                            FetchMapping()                            */
149
/************************************************************************/
150
151
void OGRElasticDataSource::FetchMapping(
152
    const char *pszIndexName, std::set<CPLString> &oSetLayers,
153
    std::vector<std::unique_ptr<OGRElasticLayer>> &apoLayers)
154
0
{
155
0
    if (oSetLayers.find(pszIndexName) != oSetLayers.end())
156
0
        return;
157
158
0
    CPLString osURL(m_osURL + CPLString("/") + pszIndexName +
159
0
                    CPLString("/_mapping?pretty"));
160
0
    json_object *poRes = RunRequest(osURL, nullptr, std::vector<int>({403}));
161
0
    if (poRes)
162
0
    {
163
0
        json_object *poLayerObj =
164
0
            CPL_json_object_object_get(poRes, pszIndexName);
165
0
        json_object *poMappings = nullptr;
166
0
        if (poLayerObj && json_object_get_type(poLayerObj) == json_type_object)
167
0
            poMappings = CPL_json_object_object_get(poLayerObj, "mappings");
168
0
        if (poMappings && json_object_get_type(poMappings) == json_type_object)
169
0
        {
170
0
            std::vector<CPLString> aosMappings;
171
0
            if (m_nMajorVersion < 7)
172
0
            {
173
0
                json_object_iter it;
174
0
                it.key = nullptr;
175
0
                it.val = nullptr;
176
0
                it.entry = nullptr;
177
0
                json_object_object_foreachC(poMappings, it)
178
0
                {
179
0
                    aosMappings.push_back(it.key);
180
0
                }
181
182
0
                if (aosMappings.size() == 1 &&
183
0
                    (aosMappings[0] == "FeatureCollection" ||
184
0
                     aosMappings[0] == "default"))
185
0
                {
186
0
                    oSetLayers.insert(pszIndexName);
187
0
                    OGRElasticLayer *poLayer = new OGRElasticLayer(
188
0
                        pszIndexName, pszIndexName, aosMappings[0], this,
189
0
                        papszOpenOptions);
190
0
                    poLayer->InitFeatureDefnFromMapping(
191
0
                        CPL_json_object_object_get(poMappings, aosMappings[0]),
192
0
                        "", std::vector<CPLString>());
193
0
                    apoLayers.push_back(
194
0
                        std::unique_ptr<OGRElasticLayer>(poLayer));
195
0
                }
196
0
                else
197
0
                {
198
0
                    for (size_t i = 0; i < aosMappings.size(); i++)
199
0
                    {
200
0
                        CPLString osLayerName(pszIndexName + CPLString("_") +
201
0
                                              aosMappings[i]);
202
0
                        if (oSetLayers.find(osLayerName) == oSetLayers.end())
203
0
                        {
204
0
                            oSetLayers.insert(osLayerName);
205
0
                            OGRElasticLayer *poLayer = new OGRElasticLayer(
206
0
                                osLayerName, pszIndexName, aosMappings[i], this,
207
0
                                papszOpenOptions);
208
0
                            poLayer->InitFeatureDefnFromMapping(
209
0
                                CPL_json_object_object_get(poMappings,
210
0
                                                           aosMappings[i]),
211
0
                                "", std::vector<CPLString>());
212
213
0
                            apoLayers.push_back(
214
0
                                std::unique_ptr<OGRElasticLayer>(poLayer));
215
0
                        }
216
0
                    }
217
0
                }
218
0
            }
219
0
            else
220
0
            {
221
0
                oSetLayers.insert(pszIndexName);
222
0
                OGRElasticLayer *poLayer = new OGRElasticLayer(
223
0
                    pszIndexName, pszIndexName, "", this, papszOpenOptions);
224
0
                poLayer->InitFeatureDefnFromMapping(poMappings, "",
225
0
                                                    std::vector<CPLString>());
226
0
                apoLayers.push_back(std::unique_ptr<OGRElasticLayer>(poLayer));
227
0
            }
228
0
        }
229
230
0
        json_object_put(poRes);
231
0
    }
232
0
}
233
234
void OGRElasticDataSource::FetchMapping(const char *pszIndexName)
235
0
{
236
0
    FetchMapping(pszIndexName, m_oSetLayers, m_apoLayers);
237
0
}
238
239
/************************************************************************/
240
/*                            GetLayerByName()                          */
241
/************************************************************************/
242
243
OGRLayer *OGRElasticDataSource::GetLayerByName(const char *pszName)
244
0
{
245
0
    const bool bIsMultipleTargetName =
246
0
        strchr(pszName, '*') != nullptr || strchr(pszName, ',') != nullptr;
247
0
    if (!m_bAllLayersListed)
248
0
    {
249
0
        for (auto &poLayer : m_apoLayers)
250
0
        {
251
0
            if (EQUAL(poLayer->GetName(), pszName))
252
0
            {
253
0
                return poLayer.get();
254
0
            }
255
0
        }
256
0
        if (!bIsMultipleTargetName)
257
0
        {
258
0
            size_t nSizeBefore = m_apoLayers.size();
259
0
            FetchMapping(pszName);
260
0
            const char *pszLastUnderscore = strrchr(pszName, '_');
261
0
            if (pszLastUnderscore && m_apoLayers.size() == nSizeBefore)
262
0
            {
263
0
                CPLString osIndexName(pszName);
264
0
                osIndexName.resize(pszLastUnderscore - pszName);
265
0
                FetchMapping(osIndexName);
266
0
            }
267
0
            for (auto &poLayer : m_apoLayers)
268
0
            {
269
0
                if (EQUAL(poLayer->GetIndexName(), pszName))
270
0
                {
271
0
                    return poLayer.get();
272
0
                }
273
0
            }
274
0
        }
275
0
    }
276
0
    else
277
0
    {
278
0
        auto poLayer = GDALDataset::GetLayerByName(pszName);
279
0
        if (poLayer)
280
0
            return poLayer;
281
0
    }
282
283
0
    if (!bIsMultipleTargetName)
284
0
    {
285
0
        return nullptr;
286
0
    }
287
288
    // Deal with wildcard layer names
289
0
    std::string osSanitizedName(pszName);
290
0
    const auto nPos = osSanitizedName.find(",-");
291
0
    if (nPos != std::string::npos)
292
0
        osSanitizedName.resize(nPos);
293
0
    const auto aosList = GetIndexList(osSanitizedName.c_str());
294
0
    if (aosList.empty() || aosList[0].find('*') != std::string::npos ||
295
0
        aosList[0].find(',') != std::string::npos)
296
0
    {
297
0
        return nullptr;
298
0
    }
299
300
    // For the sake of simplicity, take the schema of one the layers/indices
301
    // that match the wildcard.
302
    // We could potentially issue a /wildcard*/_mapping request and build a
303
    // schema that merges all mappings, but that would be more involved.
304
0
    auto poReferenceLayer =
305
0
        dynamic_cast<OGRElasticLayer *>(GetLayerByName(aosList[0].c_str()));
306
0
    if (poReferenceLayer == nullptr)
307
0
        return nullptr;
308
309
0
    m_apoLayers.push_back(
310
0
        std::make_unique<OGRElasticLayer>(pszName, poReferenceLayer));
311
0
    return m_apoLayers.back().get();
312
0
}
313
314
/************************************************************************/
315
/*                              GetLayer()                              */
316
/************************************************************************/
317
318
const OGRLayer *OGRElasticDataSource::GetLayer(int iLayer) const
319
0
{
320
0
    const int nLayers = GetLayerCount();
321
0
    if (iLayer < 0 || iLayer >= nLayers)
322
0
        return nullptr;
323
0
    else
324
0
    {
325
0
        if (m_poAggregationLayer)
326
0
            return m_poAggregationLayer.get();
327
0
        return m_apoLayers[iLayer].get();
328
0
    }
329
0
}
330
331
/************************************************************************/
332
/*                            DeleteLayer()                             */
333
/************************************************************************/
334
335
OGRErr OGRElasticDataSource::DeleteLayer(int iLayer)
336
337
0
{
338
0
    if (eAccess != GA_Update)
339
0
    {
340
0
        CPLError(CE_Failure, CPLE_AppDefined,
341
0
                 "Dataset opened in read-only mode");
342
0
        return OGRERR_FAILURE;
343
0
    }
344
345
0
    GetLayerCount();
346
0
    if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
347
0
        return OGRERR_FAILURE;
348
349
    /* -------------------------------------------------------------------- */
350
    /*      Blow away our OGR structures related to the layer.  This is     */
351
    /*      pretty dangerous if anything has a reference to this layer!     */
352
    /* -------------------------------------------------------------------- */
353
0
    CPLString osLayerName = m_apoLayers[iLayer]->GetName();
354
0
    CPLString osIndex = m_apoLayers[iLayer]->GetIndexName();
355
0
    CPLString osMapping = m_apoLayers[iLayer]->GetMappingName();
356
357
0
    bool bSeveralMappings = false;
358
0
    json_object *poIndexResponse =
359
0
        RunRequest(CPLSPrintf("%s/%s", GetURL(), osIndex.c_str()), nullptr);
360
0
    if (poIndexResponse)
361
0
    {
362
0
        json_object *poIndex =
363
0
            CPL_json_object_object_get(poIndexResponse, osMapping);
364
0
        if (poIndex != nullptr)
365
0
        {
366
0
            json_object *poMappings =
367
0
                CPL_json_object_object_get(poIndex, "mappings");
368
0
            if (poMappings != nullptr)
369
0
            {
370
0
                bSeveralMappings = json_object_object_length(poMappings) > 1;
371
0
            }
372
0
        }
373
0
        json_object_put(poIndexResponse);
374
0
    }
375
    // Deletion of one mapping in an index was supported in ES 1.X, but
376
    // considered unsafe and removed in later versions
377
0
    if (bSeveralMappings)
378
0
    {
379
0
        CPLError(CE_Failure, CPLE_AppDefined,
380
0
                 "%s/%s already exists, but other mappings also exist in "
381
0
                 "this index. "
382
0
                 "You have to delete the whole index.",
383
0
                 osIndex.c_str(), osMapping.c_str());
384
0
        return OGRERR_FAILURE;
385
0
    }
386
387
0
    CPLDebug("ES", "DeleteLayer(%s)", osLayerName.c_str());
388
389
0
    m_oSetLayers.erase(osLayerName);
390
0
    m_apoLayers.erase(m_apoLayers.begin() + iLayer);
391
392
0
    Delete(CPLSPrintf("%s/%s", GetURL(), osIndex.c_str()));
393
394
0
    return OGRERR_NONE;
395
0
}
396
397
/************************************************************************/
398
/*                           ICreateLayer()                             */
399
/************************************************************************/
400
401
OGRLayer *
402
OGRElasticDataSource::ICreateLayer(const char *pszLayerName,
403
                                   const OGRGeomFieldDefn *poGeomFieldDefn,
404
                                   CSLConstList papszOptions)
405
0
{
406
0
    if (eAccess != GA_Update)
407
0
    {
408
0
        CPLError(CE_Failure, CPLE_AppDefined,
409
0
                 "Dataset opened in read-only mode");
410
0
        return nullptr;
411
0
    }
412
413
0
    const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
414
0
    const auto poSRS =
415
0
        poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
416
417
0
    CPLString osLaunderedName(pszLayerName);
418
419
0
    const char *pszIndexName = CSLFetchNameValue(papszOptions, "INDEX_NAME");
420
0
    if (pszIndexName != nullptr)
421
0
        osLaunderedName = pszIndexName;
422
423
0
    for (size_t i = 0; i < osLaunderedName.size(); i++)
424
0
    {
425
0
        if (osLaunderedName[i] >= 'A' && osLaunderedName[i] <= 'Z')
426
0
            osLaunderedName[i] += 'a' - 'A';
427
0
        else if (osLaunderedName[i] == '/' || osLaunderedName[i] == '?')
428
0
            osLaunderedName[i] = '_';
429
0
    }
430
0
    if (strcmp(osLaunderedName.c_str(), pszLayerName) != 0)
431
0
        CPLDebug("ES", "Laundered layer name to %s", osLaunderedName.c_str());
432
433
    // Backup error state
434
0
    CPLErr eLastErrorType = CPLGetLastErrorType();
435
0
    CPLErrorNum nLastErrorNo = CPLGetLastErrorNo();
436
0
    CPLString osLastErrorMsg = CPLGetLastErrorMsg();
437
438
0
    const char *pszMappingName =
439
0
        m_nMajorVersion < 7 ? CSLFetchNameValueDef(papszOptions, "MAPPING_NAME",
440
0
                                                   "FeatureCollection")
441
0
                            : nullptr;
442
443
    // Check if the index and mapping exists
444
0
    bool bIndexExists = false;
445
0
    bool bMappingExists = false;
446
0
    bool bSeveralMappings = false;
447
0
    CPLPushErrorHandler(CPLQuietErrorHandler);
448
0
    json_object *poIndexResponse = RunRequest(
449
0
        CPLSPrintf("%s/%s", GetURL(), osLaunderedName.c_str()), nullptr);
450
0
    CPLPopErrorHandler();
451
452
    // Restore error state
453
0
    CPLErrorSetState(eLastErrorType, nLastErrorNo, osLastErrorMsg);
454
455
0
    if (poIndexResponse)
456
0
    {
457
0
        bIndexExists = true;
458
0
        json_object *poIndex =
459
0
            CPL_json_object_object_get(poIndexResponse, osLaunderedName);
460
0
        if (m_nMajorVersion < 7)
461
0
        {
462
0
            if (poIndex != nullptr)
463
0
            {
464
0
                json_object *poMappings =
465
0
                    CPL_json_object_object_get(poIndex, "mappings");
466
0
                if (poMappings != nullptr)
467
0
                {
468
0
                    bMappingExists = CPL_json_object_object_get(
469
0
                                         poMappings, pszMappingName) != nullptr;
470
0
                    bSeveralMappings =
471
0
                        json_object_object_length(poMappings) > 1;
472
0
                }
473
0
            }
474
0
        }
475
0
        else
476
0
        {
477
            // Indexes in Elasticsearch 7+ can not have multiple types,
478
            // so essentially this will always be true.
479
0
            bMappingExists = true;
480
0
        }
481
0
        json_object_put(poIndexResponse);
482
0
    }
483
484
0
    if (bMappingExists)
485
0
    {
486
0
        if (CPLFetchBool(papszOptions, "OVERWRITE_INDEX", false))
487
0
        {
488
0
            Delete(CPLSPrintf("%s/%s", GetURL(), osLaunderedName.c_str()));
489
0
            bIndexExists = false;
490
0
        }
491
0
        else if (m_bOverwrite || CPLFetchBool(papszOptions, "OVERWRITE", false))
492
0
        {
493
            // Deletion of one mapping in an index was supported in ES 1.X, but
494
            // considered unsafe and removed in later versions
495
0
            if (m_nMajorVersion >= 7)
496
0
            {
497
0
                CPLError(CE_Failure, CPLE_AppDefined,
498
0
                         "The index %s already exists. "
499
0
                         "You have to delete the whole index. You can do that "
500
0
                         "with OVERWRITE_INDEX=YES",
501
0
                         osLaunderedName.c_str());
502
0
                return nullptr;
503
0
            }
504
0
            else if (bSeveralMappings)
505
0
            {
506
0
                CPLError(CE_Failure, CPLE_AppDefined,
507
0
                         "%s/%s already exists, but other mappings also exist "
508
0
                         "in this index. "
509
0
                         "You have to delete the whole index. You can do that "
510
0
                         "with OVERWRITE_INDEX=YES",
511
0
                         osLaunderedName.c_str(), pszMappingName);
512
0
                return nullptr;
513
0
            }
514
0
            Delete(CPLSPrintf("%s/%s", GetURL(), osLaunderedName.c_str()));
515
0
            bIndexExists = false;
516
0
        }
517
0
        else
518
0
        {
519
0
            if (m_nMajorVersion < 7)
520
0
            {
521
0
                CPLError(CE_Failure, CPLE_AppDefined, "%s/%s already exists",
522
0
                         osLaunderedName.c_str(), pszMappingName);
523
0
            }
524
0
            else
525
0
            {
526
0
                CPLError(CE_Failure, CPLE_AppDefined, "%s already exists",
527
0
                         osLaunderedName.c_str());
528
0
            }
529
0
            return nullptr;
530
0
        }
531
0
    }
532
533
    // Create the index
534
0
    if (!bIndexExists)
535
0
    {
536
0
        CPLString osIndexURL(
537
0
            CPLSPrintf("%s/%s", GetURL(), osLaunderedName.c_str()));
538
539
        // If we have a user specified index definition, use it
540
0
        const char *pszDef =
541
0
            CSLFetchNameValue(papszOptions, "INDEX_DEFINITION");
542
0
        CPLString osDef;
543
0
        if (pszDef != nullptr)
544
0
        {
545
0
            osDef = pszDef;
546
0
            if (strchr(pszDef, '{') == nullptr)
547
0
            {
548
0
                VSILFILE *fp = VSIFOpenL(pszDef, "rb");
549
0
                if (fp)
550
0
                {
551
0
                    GByte *pabyRet = nullptr;
552
0
                    CPL_IGNORE_RET_VAL(
553
0
                        VSIIngestFile(fp, pszDef, &pabyRet, nullptr, -1));
554
0
                    if (pabyRet)
555
0
                    {
556
0
                        osDef = reinterpret_cast<char *>(pabyRet);
557
0
                        VSIFree(pabyRet);
558
0
                    }
559
0
                    VSIFCloseL(fp);
560
0
                }
561
0
            }
562
0
        }
563
0
        if (!UploadFile(osIndexURL, osDef.c_str(), "PUT"))
564
0
            return nullptr;
565
0
    }
566
567
    // If we have a user specified mapping, then go ahead and update it now
568
0
    const char *pszLayerMapping =
569
0
        CSLFetchNameValueDef(papszOptions, "MAPPING", m_pszMapping);
570
0
    if (pszLayerMapping != nullptr)
571
0
    {
572
0
        CPLString osLayerMapping(pszLayerMapping);
573
0
        if (strchr(pszLayerMapping, '{') == nullptr)
574
0
        {
575
0
            VSILFILE *fp = VSIFOpenL(pszLayerMapping, "rb");
576
0
            if (fp)
577
0
            {
578
0
                GByte *pabyRet = nullptr;
579
0
                CPL_IGNORE_RET_VAL(
580
0
                    VSIIngestFile(fp, pszLayerMapping, &pabyRet, nullptr, -1));
581
0
                if (pabyRet)
582
0
                {
583
0
                    osLayerMapping = reinterpret_cast<char *>(pabyRet);
584
0
                    VSIFree(pabyRet);
585
0
                }
586
0
                VSIFCloseL(fp);
587
0
            }
588
0
        }
589
590
0
        CPLString osMappingURL =
591
0
            CPLSPrintf("%s/%s/_mapping", GetURL(), osLaunderedName.c_str());
592
0
        if (m_nMajorVersion < 7)
593
0
            osMappingURL += CPLSPrintf("/%s", pszMappingName);
594
0
        if (!UploadFile(osMappingURL, osLayerMapping.c_str()))
595
0
        {
596
0
            return nullptr;
597
0
        }
598
0
    }
599
600
0
    OGRElasticLayer *poLayer =
601
0
        new OGRElasticLayer(osLaunderedName.c_str(), osLaunderedName.c_str(),
602
0
                            pszMappingName, this, papszOptions);
603
0
    poLayer->FinalizeFeatureDefn(false);
604
605
0
    if (eGType != wkbNone)
606
0
    {
607
0
        const char *pszGeometryName =
608
0
            CSLFetchNameValueDef(papszOptions, "GEOMETRY_NAME", "geometry");
609
0
        OGRGeomFieldDefn oFieldDefn(pszGeometryName, eGType);
610
0
        if (poSRS)
611
0
        {
612
0
            OGRSpatialReference *poSRSClone = poSRS->Clone();
613
0
            poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
614
0
            oFieldDefn.SetSpatialRef(poSRSClone);
615
0
            poSRSClone->Release();
616
0
        }
617
0
        poLayer->CreateGeomField(&oFieldDefn, FALSE);
618
0
    }
619
0
    if (pszLayerMapping)
620
0
        poLayer->SetManualMapping();
621
622
0
    poLayer->SetIgnoreSourceID(
623
0
        CPLFetchBool(papszOptions, "IGNORE_SOURCE_ID", false));
624
0
    poLayer->SetDotAsNestedField(
625
0
        CPLFetchBool(papszOptions, "DOT_AS_NESTED_FIELD", true));
626
0
    poLayer->SetFID(CSLFetchNameValueDef(papszOptions, "FID", "ogc_fid"));
627
0
    poLayer->SetNextFID(0);
628
629
0
    m_oSetLayers.insert(poLayer->GetName());
630
0
    m_apoLayers.push_back(std::unique_ptr<OGRElasticLayer>(poLayer));
631
632
0
    return poLayer;
633
0
}
634
635
/************************************************************************/
636
/*                               HTTPFetch()                            */
637
/************************************************************************/
638
639
CPLHTTPResult *OGRElasticDataSource::HTTPFetch(const char *pszURL,
640
                                               CSLConstList papszOptions)
641
31
{
642
31
    CPLStringList aosOptions(papszOptions);
643
31
    if (!m_osUserPwd.empty())
644
0
        aosOptions.SetNameValue("USERPWD", m_osUserPwd.c_str());
645
31
    if (!m_oMapHeadersFromEnv.empty())
646
0
    {
647
0
        const char *pszExistingHeaders = aosOptions.FetchNameValue("HEADERS");
648
0
        std::string osHeaders;
649
0
        if (pszExistingHeaders)
650
0
        {
651
0
            osHeaders += pszExistingHeaders;
652
0
            osHeaders += '\n';
653
0
        }
654
0
        for (const auto &kv : m_oMapHeadersFromEnv)
655
0
        {
656
0
            const char *pszValueFromEnv =
657
0
                CPLGetConfigOption(kv.second.c_str(), nullptr);
658
0
            if (pszValueFromEnv)
659
0
            {
660
0
                osHeaders += kv.first;
661
0
                osHeaders += ": ";
662
0
                osHeaders += pszValueFromEnv;
663
0
                osHeaders += '\n';
664
0
            }
665
0
        }
666
0
        aosOptions.SetNameValue("HEADERS", osHeaders.c_str());
667
0
    }
668
669
31
    return CPLHTTPFetch(pszURL, aosOptions);
670
31
}
671
672
/************************************************************************/
673
/*                               RunRequest()                           */
674
/************************************************************************/
675
676
json_object *
677
OGRElasticDataSource::RunRequest(const char *pszURL, const char *pszPostContent,
678
                                 const std::vector<int> &anSilentedHTTPErrors)
679
31
{
680
31
    char **papszOptions = nullptr;
681
682
31
    if (pszPostContent && pszPostContent[0])
683
0
    {
684
0
        papszOptions =
685
0
            CSLSetNameValue(papszOptions, "POSTFIELDS", pszPostContent);
686
0
        papszOptions =
687
0
            CSLAddNameValue(papszOptions, "HEADERS",
688
0
                            "Content-Type: application/json; charset=UTF-8");
689
0
    }
690
691
31
    CPLPushErrorHandler(CPLQuietErrorHandler);
692
31
    CPLHTTPResult *psResult = HTTPFetch(pszURL, papszOptions);
693
31
    CPLPopErrorHandler();
694
31
    CSLDestroy(papszOptions);
695
696
31
    if (psResult->pszErrBuf != nullptr)
697
31
    {
698
31
        CPLString osErrorMsg(psResult->pabyData
699
31
                                 ? (const char *)psResult->pabyData
700
31
                                 : psResult->pszErrBuf);
701
31
        bool bSilence = false;
702
31
        for (auto nCode : anSilentedHTTPErrors)
703
0
        {
704
0
            if (strstr(psResult->pszErrBuf, CPLSPrintf("%d", nCode)))
705
0
            {
706
0
                bSilence = true;
707
0
                break;
708
0
            }
709
0
        }
710
31
        if (bSilence)
711
0
        {
712
0
            CPLDebug("ES", "%s", osErrorMsg.c_str());
713
0
        }
714
31
        else
715
31
        {
716
31
            CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrorMsg.c_str());
717
31
        }
718
31
        CPLHTTPDestroyResult(psResult);
719
31
        return nullptr;
720
31
    }
721
722
0
    if (psResult->pabyData == nullptr)
723
0
    {
724
0
        CPLError(CE_Failure, CPLE_AppDefined,
725
0
                 "Empty content returned by server");
726
0
        CPLHTTPDestroyResult(psResult);
727
0
        return nullptr;
728
0
    }
729
730
0
    if (STARTS_WITH((const char *)psResult->pabyData, "{\"error\":"))
731
0
    {
732
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s",
733
0
                 (const char *)psResult->pabyData);
734
0
        CPLHTTPDestroyResult(psResult);
735
0
        return nullptr;
736
0
    }
737
738
0
    json_object *poObj = nullptr;
739
0
    const char *pszText = reinterpret_cast<const char *>(psResult->pabyData);
740
0
    if (!OGRJSonParse(pszText, &poObj, true))
741
0
    {
742
0
        CPLHTTPDestroyResult(psResult);
743
0
        return nullptr;
744
0
    }
745
746
0
    CPLHTTPDestroyResult(psResult);
747
748
0
    if (json_object_get_type(poObj) != json_type_object)
749
0
    {
750
0
        CPLError(CE_Failure, CPLE_AppDefined,
751
0
                 "Return is not a JSON dictionary");
752
0
        json_object_put(poObj);
753
0
        poObj = nullptr;
754
0
    }
755
756
0
    return poObj;
757
0
}
758
759
/************************************************************************/
760
/*                           CheckVersion()                             */
761
/************************************************************************/
762
763
bool OGRElasticDataSource::CheckVersion()
764
31
{
765
31
    json_object *poMainInfo = RunRequest(m_osURL);
766
31
    if (poMainInfo == nullptr)
767
31
        return false;
768
0
    bool bVersionFound = false;
769
0
    json_object *poVersion = CPL_json_object_object_get(poMainInfo, "version");
770
0
    if (poVersion != nullptr)
771
0
    {
772
0
        json_object *poNumber = CPL_json_object_object_get(poVersion, "number");
773
0
        if (poNumber != nullptr &&
774
0
            json_object_get_type(poNumber) == json_type_string)
775
0
        {
776
0
            bVersionFound = true;
777
0
            const char *pszVersion = json_object_get_string(poNumber);
778
0
            CPLDebug("ES", "Server version: %s", pszVersion);
779
0
            m_nMajorVersion = atoi(pszVersion);
780
0
            const char *pszDot = strchr(pszVersion, '.');
781
0
            if (pszDot)
782
0
                m_nMinorVersion = atoi(pszDot + 1);
783
0
        }
784
0
    }
785
0
    json_object_put(poMainInfo);
786
0
    if (!bVersionFound)
787
0
    {
788
0
        CPLError(CE_Failure, CPLE_AppDefined, "Server version not found");
789
0
        return false;
790
0
    }
791
0
    if (m_nMajorVersion < 1 || m_nMajorVersion > 7)
792
0
    {
793
0
        CPLDebug("ES", "Server version untested with current driver");
794
0
    }
795
0
    return true;
796
0
}
797
798
/************************************************************************/
799
/*                           OpenAggregation()                          */
800
/************************************************************************/
801
802
bool OGRElasticDataSource::OpenAggregation(const char *pszAggregation)
803
0
{
804
0
    m_bAllLayersListed = true;
805
0
    m_poAggregationLayer =
806
0
        OGRElasticAggregationLayer::Build(this, pszAggregation);
807
0
    return m_poAggregationLayer != nullptr;
808
0
}
809
810
/************************************************************************/
811
/*                                Open()                                */
812
/************************************************************************/
813
814
bool OGRElasticDataSource::Open(GDALOpenInfo *poOpenInfo)
815
31
{
816
31
    eAccess = poOpenInfo->eAccess;
817
31
    m_pszName = CPLStrdup(poOpenInfo->pszFilename);
818
31
    m_osURL = (STARTS_WITH_CI(m_pszName, "ES:")) ? m_pszName + 3 : m_pszName;
819
31
    if (m_osURL.empty())
820
0
    {
821
0
        const char *pszHost = CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
822
0
                                                   "HOST", "localhost");
823
0
        m_osURL = pszHost;
824
0
        m_osURL += ":";
825
0
        const char *pszPort =
826
0
            CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "PORT", "9200");
827
0
        m_osURL += pszPort;
828
0
    }
829
31
    m_osUserPwd =
830
31
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "USERPWD", "");
831
31
    m_nBatchSize = atoi(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
832
31
                                             "BATCH_SIZE", "100"));
833
31
    m_nFeatureCountToEstablishFeatureDefn = atoi(
834
31
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
835
31
                             "FEATURE_COUNT_TO_ESTABLISH_FEATURE_DEFN", "100"));
836
31
    m_bJSonField =
837
31
        CPLFetchBool(poOpenInfo->papszOpenOptions, "JSON_FIELD", false);
838
31
    m_bFlattenNestedAttributes = CPLFetchBool(
839
31
        poOpenInfo->papszOpenOptions, "FLATTEN_NESTED_ATTRIBUTES", true);
840
31
    m_osFID =
841
31
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "FID", "ogc_fid");
842
843
    // Only used for wildcard layers
844
31
    m_bAddSourceIndexName = CPLFetchBool(poOpenInfo->papszOpenOptions,
845
31
                                         "ADD_SOURCE_INDEX_NAME", false);
846
847
31
    const char *pszHeadersFromEnv =
848
31
        CPLGetConfigOption("ES_FORWARD_HTTP_HEADERS_FROM_ENV",
849
31
                           CSLFetchNameValue(poOpenInfo->papszOpenOptions,
850
31
                                             "FORWARD_HTTP_HEADERS_FROM_ENV"));
851
31
    if (pszHeadersFromEnv)
852
0
    {
853
0
        CPLStringList aosTokens(CSLTokenizeString2(pszHeadersFromEnv, ",", 0));
854
0
        for (int i = 0; i < aosTokens.size(); ++i)
855
0
        {
856
0
            char *pszKey = nullptr;
857
0
            const char *pszValue = CPLParseNameValue(aosTokens[i], &pszKey);
858
0
            if (pszKey && pszValue)
859
0
            {
860
0
                m_oMapHeadersFromEnv[pszKey] = pszValue;
861
0
            }
862
0
            CPLFree(pszKey);
863
0
        }
864
0
    }
865
866
31
    if (!CheckVersion())
867
31
        return false;
868
869
0
    const char *pszLayerName =
870
0
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "LAYER");
871
0
    const char *pszAggregation =
872
0
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "AGGREGATION");
873
0
    if (pszLayerName && pszAggregation)
874
0
    {
875
0
        CPLError(CE_Failure, CPLE_NotSupported,
876
0
                 "LAYER and AGGREGATION open options are mutually exclusive");
877
0
        return false;
878
0
    }
879
880
0
    if (pszLayerName)
881
0
    {
882
0
        bool bFound = GetLayerByName(pszLayerName) != nullptr;
883
0
        m_bAllLayersListed = true;
884
0
        return bFound;
885
0
    }
886
887
0
    if (pszAggregation)
888
0
        return OpenAggregation(pszAggregation);
889
890
0
    return true;
891
0
}
892
893
/************************************************************************/
894
/*                             Delete()                                 */
895
/************************************************************************/
896
897
void OGRElasticDataSource::Delete(const CPLString &url)
898
0
{
899
0
    char **papszOptions = nullptr;
900
0
    papszOptions = CSLAddNameValue(papszOptions, "CUSTOMREQUEST", "DELETE");
901
0
    CPLHTTPResult *psResult = HTTPFetch(url, papszOptions);
902
0
    CSLDestroy(papszOptions);
903
0
    if (psResult)
904
0
    {
905
0
        CPLHTTPDestroyResult(psResult);
906
0
    }
907
0
}
908
909
/************************************************************************/
910
/*                            UploadFile()                              */
911
/************************************************************************/
912
913
bool OGRElasticDataSource::UploadFile(const CPLString &url,
914
                                      const CPLString &data,
915
                                      const CPLString &osVerb)
916
0
{
917
0
    bool bRet = true;
918
0
    char **papszOptions = nullptr;
919
0
    if (!osVerb.empty())
920
0
    {
921
0
        papszOptions =
922
0
            CSLAddNameValue(papszOptions, "CUSTOMREQUEST", osVerb.c_str());
923
0
    }
924
0
    if (data.empty())
925
0
    {
926
0
        if (osVerb.empty())
927
0
        {
928
0
            papszOptions =
929
0
                CSLAddNameValue(papszOptions, "CUSTOMREQUEST", "PUT");
930
0
        }
931
0
    }
932
0
    else
933
0
    {
934
0
        papszOptions =
935
0
            CSLAddNameValue(papszOptions, "POSTFIELDS", data.c_str());
936
0
        papszOptions =
937
0
            CSLAddNameValue(papszOptions, "HEADERS",
938
0
                            "Content-Type: application/json; charset=UTF-8");
939
0
    }
940
941
0
    CPLHTTPResult *psResult = HTTPFetch(url, papszOptions);
942
0
    CSLDestroy(papszOptions);
943
0
    if (psResult)
944
0
    {
945
0
        if (psResult->pszErrBuf != nullptr ||
946
0
            (psResult->pabyData &&
947
0
             STARTS_WITH((const char *)psResult->pabyData, "{\"error\":")) ||
948
0
            (psResult->pabyData && strstr((const char *)psResult->pabyData,
949
0
                                          "\"errors\":true,") != nullptr))
950
0
        {
951
0
            bRet = false;
952
0
            CPLError(CE_Failure, CPLE_AppDefined, "%s",
953
0
                     psResult->pabyData ? (const char *)psResult->pabyData
954
0
                                        : psResult->pszErrBuf);
955
0
        }
956
0
        CPLHTTPDestroyResult(psResult);
957
0
    }
958
0
    return bRet;
959
0
}
960
961
/************************************************************************/
962
/*                               Create()                               */
963
/************************************************************************/
964
965
int OGRElasticDataSource::Create(const char *pszFilename,
966
                                 CPL_UNUSED char **papszOptions)
967
0
{
968
0
    eAccess = GA_Update;
969
0
    m_pszName = CPLStrdup(pszFilename);
970
0
    m_osURL =
971
0
        (STARTS_WITH_CI(pszFilename, "ES:")) ? pszFilename + 3 : pszFilename;
972
0
    if (m_osURL.empty())
973
0
        m_osURL = "localhost:9200";
974
975
0
    const char *pszMetaFile = CPLGetConfigOption("ES_META", nullptr);
976
0
    m_bOverwrite = CPLTestBool(CPLGetConfigOption("ES_OVERWRITE", "0"));
977
    // coverity[tainted_data]
978
0
    m_nBulkUpload = (int)CPLAtof(CPLGetConfigOption("ES_BULK", "0"));
979
980
    // Read in the meta file from disk
981
0
    if (pszMetaFile != nullptr)
982
0
    {
983
0
        VSILFILE *fp = VSIFOpenL(pszMetaFile, "rb");
984
0
        if (fp)
985
0
        {
986
0
            GByte *pabyRet = nullptr;
987
0
            CPL_IGNORE_RET_VAL(
988
0
                VSIIngestFile(fp, pszMetaFile, &pabyRet, nullptr, -1));
989
0
            if (pabyRet)
990
0
            {
991
0
                m_pszMapping = (char *)pabyRet;
992
0
            }
993
0
            VSIFCloseL(fp);
994
0
        }
995
0
    }
996
997
0
    return CheckVersion();
998
0
}
999
1000
/************************************************************************/
1001
/*                           GetLayerIndex()                            */
1002
/************************************************************************/
1003
1004
int OGRElasticDataSource::GetLayerIndex(const char *pszName)
1005
0
{
1006
0
    GetLayerCount();
1007
0
    for (int i = 0; i < static_cast<int>(m_apoLayers.size()); ++i)
1008
0
    {
1009
0
        if (strcmp(m_apoLayers[i]->GetName(), pszName) == 0)
1010
0
            return i;
1011
0
    }
1012
0
    for (int i = 0; i < static_cast<int>(m_apoLayers.size()); ++i)
1013
0
    {
1014
0
        if (EQUAL(m_apoLayers[i]->GetName(), pszName))
1015
0
            return i;
1016
0
    }
1017
0
    return -1;
1018
0
}
1019
1020
/************************************************************************/
1021
/*                             ExecuteSQL()                             */
1022
/************************************************************************/
1023
1024
OGRLayer *OGRElasticDataSource::ExecuteSQL(const char *pszSQLCommand,
1025
                                           OGRGeometry *poSpatialFilter,
1026
                                           const char *pszDialect)
1027
0
{
1028
0
    GetLayerCount();
1029
0
    for (auto &poLayer : m_apoLayers)
1030
0
    {
1031
0
        poLayer->SyncToDisk();
1032
0
    }
1033
1034
    /* -------------------------------------------------------------------- */
1035
    /*      Special case DELLAYER: command.                                 */
1036
    /* -------------------------------------------------------------------- */
1037
0
    if (STARTS_WITH_CI(pszSQLCommand, "DELLAYER:"))
1038
0
    {
1039
0
        const char *pszLayerName = pszSQLCommand + 9;
1040
1041
0
        while (*pszLayerName == ' ')
1042
0
            pszLayerName++;
1043
1044
0
        for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
1045
0
             iLayer++)
1046
0
        {
1047
0
            if (EQUAL(m_apoLayers[iLayer]->GetName(), pszLayerName))
1048
0
            {
1049
0
                DeleteLayer(iLayer);
1050
0
                break;
1051
0
            }
1052
0
        }
1053
0
        return nullptr;
1054
0
    }
1055
1056
0
    if (pszDialect != nullptr && EQUAL(pszDialect, "ES"))
1057
0
    {
1058
0
        return new OGRElasticLayer("RESULT", nullptr, nullptr, this,
1059
0
                                   papszOpenOptions, pszSQLCommand);
1060
0
    }
1061
1062
    /* -------------------------------------------------------------------- */
1063
    /*      Deal with "SELECT xxxx ORDER BY" statement                      */
1064
    /* -------------------------------------------------------------------- */
1065
0
    if (STARTS_WITH_CI(pszSQLCommand, "SELECT"))
1066
0
    {
1067
0
        swq_select *psSelectInfo = new swq_select();
1068
0
        if (psSelectInfo->preparse(pszSQLCommand, TRUE) != CE_None)
1069
0
        {
1070
0
            delete psSelectInfo;
1071
0
            return nullptr;
1072
0
        }
1073
1074
0
        int iLayer = 0;
1075
0
        if (psSelectInfo->table_count == 1 &&
1076
0
            psSelectInfo->table_defs[0].data_source == nullptr &&
1077
0
            (iLayer = GetLayerIndex(psSelectInfo->table_defs[0].table_name)) >=
1078
0
                0 &&
1079
0
            psSelectInfo->join_count == 0 && psSelectInfo->order_specs > 0 &&
1080
0
            psSelectInfo->poOtherSelect == nullptr)
1081
0
        {
1082
0
            OGRElasticLayer *poSrcLayer = m_apoLayers[iLayer].get();
1083
0
            std::vector<OGRESSortDesc> aoSortColumns;
1084
0
            int i = 0;  // Used after for.
1085
0
            for (; i < psSelectInfo->order_specs; i++)
1086
0
            {
1087
0
                int nFieldIndex = poSrcLayer->GetLayerDefn()->GetFieldIndex(
1088
0
                    psSelectInfo->order_defs[i].field_name);
1089
0
                if (nFieldIndex < 0)
1090
0
                    break;
1091
1092
                /* Make sure to have the right case */
1093
0
                const char *pszFieldName = poSrcLayer->GetLayerDefn()
1094
0
                                               ->GetFieldDefn(nFieldIndex)
1095
0
                                               ->GetNameRef();
1096
1097
0
                aoSortColumns.emplace_back(
1098
0
                    pszFieldName,
1099
0
                    CPL_TO_BOOL(psSelectInfo->order_defs[i].ascending_flag));
1100
0
            }
1101
1102
0
            if (i == psSelectInfo->order_specs)
1103
0
            {
1104
0
                OGRElasticLayer *poDupLayer = poSrcLayer->Clone();
1105
1106
0
                poDupLayer->SetOrderBy(aoSortColumns);
1107
0
                int nBackup = psSelectInfo->order_specs;
1108
0
                psSelectInfo->order_specs = 0;
1109
0
                char *pszSQLWithoutOrderBy = psSelectInfo->Unparse();
1110
0
                CPLDebug("ES", "SQL without ORDER BY: %s",
1111
0
                         pszSQLWithoutOrderBy);
1112
0
                psSelectInfo->order_specs = nBackup;
1113
0
                delete psSelectInfo;
1114
0
                psSelectInfo = nullptr;
1115
1116
                /* Just set poDupLayer in the papoLayers for the time of the */
1117
                /* base ExecuteSQL(), so that the OGRGenSQLResultsLayer */
1118
                /* references  that temporary layer */
1119
0
                m_apoLayers[iLayer].release();
1120
0
                m_apoLayers[iLayer].reset(poDupLayer);
1121
1122
0
                OGRLayer *poResLayer = GDALDataset::ExecuteSQL(
1123
0
                    pszSQLWithoutOrderBy, poSpatialFilter, pszDialect);
1124
0
                m_apoLayers[iLayer].release();
1125
0
                m_apoLayers[iLayer].reset(poSrcLayer);
1126
1127
0
                CPLFree(pszSQLWithoutOrderBy);
1128
1129
0
                if (poResLayer != nullptr)
1130
0
                    m_oMapResultSet[poResLayer] = poDupLayer;
1131
0
                else
1132
0
                    delete poDupLayer;
1133
0
                return poResLayer;
1134
0
            }
1135
0
        }
1136
0
        delete psSelectInfo;
1137
0
    }
1138
1139
0
    return GDALDataset::ExecuteSQL(pszSQLCommand, poSpatialFilter, pszDialect);
1140
0
}
1141
1142
/************************************************************************/
1143
/*                          ReleaseResultSet()                          */
1144
/************************************************************************/
1145
1146
void OGRElasticDataSource::ReleaseResultSet(OGRLayer *poResultsSet)
1147
0
{
1148
0
    if (poResultsSet == nullptr)
1149
0
        return;
1150
1151
0
    std::map<OGRLayer *, OGRLayer *>::iterator oIter =
1152
0
        m_oMapResultSet.find(poResultsSet);
1153
0
    if (oIter != m_oMapResultSet.end())
1154
0
    {
1155
        /* Destroy first the result layer, because it still references */
1156
        /* the poDupLayer (oIter->second) */
1157
0
        delete poResultsSet;
1158
1159
0
        delete oIter->second;
1160
0
        m_oMapResultSet.erase(oIter);
1161
0
    }
1162
0
    else
1163
0
    {
1164
0
        delete poResultsSet;
1165
0
    }
1166
0
}