Coverage Report

Created: 2026-03-30 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/amigocloud/ogramigocloudtablelayer.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  AmigoCloud Translator
4
 * Purpose:  Implements OGRAmigoCloudTableLayer class.
5
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2015, Victor Chernetsky, <victor at amigocloud dot com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_multiproc.h"  // CPLSleep()
14
15
#include "ogr_amigocloud.h"
16
#include "ogr_p.h"
17
#include "ogr_pgdump.h"
18
#include "ogrlibjsonutils.h"
19
#include <sstream>
20
#include <iomanip>
21
22
/************************************************************************/
23
/*                   OGRAMIGOCLOUDEscapeIdentifier( )                   */
24
/************************************************************************/
25
26
CPLString OGRAMIGOCLOUDEscapeIdentifier(const char *pszStr)
27
0
{
28
0
    CPLString osStr;
29
30
0
    osStr += "\"";
31
32
0
    char ch = '\0';
33
0
    for (int i = 0; (ch = pszStr[i]) != '\0'; i++)
34
0
    {
35
0
        if (ch == '"')
36
0
            osStr.append(1, ch);
37
0
        osStr.append(1, ch);
38
0
    }
39
40
0
    osStr += "\"";
41
42
0
    return osStr;
43
0
}
44
45
std::string OGRAMIGOCLOUDJsonEncode(const std::string &s)
46
0
{
47
0
    std::ostringstream o;
48
0
    for (auto c = s.cbegin(); c != s.cend(); c++)
49
0
    {
50
0
        switch (*c)
51
0
        {
52
0
            case '"':
53
0
                o << "\\\"";
54
0
                break;
55
0
            case '\\':
56
0
                o << "\\\\";
57
0
                break;
58
0
            case '\b':
59
0
                o << "\\b";
60
0
                break;
61
0
            case '\f':
62
0
                o << "\\f";
63
0
                break;
64
0
            case '\n':
65
0
                o << "\\n";
66
0
                break;
67
0
            case '\r':
68
0
                o << "\\r";
69
0
                break;
70
0
            case '\t':
71
0
                o << "\\t";
72
0
                break;
73
0
            default:
74
0
                if (*c <= '\x1f')
75
0
                {
76
0
                    o << "\\u" << std::hex << std::setw(4) << std::setfill('0')
77
0
                      << (int)*c;
78
0
                }
79
0
                else
80
0
                {
81
0
                    o << *c;
82
0
                }
83
0
        }
84
0
    }
85
0
    return o.str();
86
0
}
87
88
/************************************************************************/
89
/*                      OGRAmigoCloudTableLayer()                       */
90
/************************************************************************/
91
92
OGRAmigoCloudTableLayer::OGRAmigoCloudTableLayer(
93
    OGRAmigoCloudDataSource *poDSIn, const char *pszName)
94
0
    : OGRAmigoCloudLayer(poDSIn), osDatasetId(CPLString(pszName)), nNextFID(-1),
95
0
      bDeferredCreation(FALSE)
96
0
{
97
0
    osTableName = CPLString("dataset_") + osDatasetId;
98
0
    SetDescription(osDatasetId);
99
0
    osName = osDatasetId;
100
0
    nMaxChunkSize =
101
0
        atoi(CPLGetConfigOption("AMIGOCLOUD_MAX_CHUNK_SIZE", "15")) * 1024 *
102
0
        1024;
103
0
}
104
105
/************************************************************************/
106
/*                      ~OGRAmigoCloudTableLayer()                      */
107
/************************************************************************/
108
109
OGRAmigoCloudTableLayer::~OGRAmigoCloudTableLayer()
110
111
0
{
112
0
    if (bDeferredCreation)
113
0
        RunDeferredCreationIfNecessary();
114
0
    FlushDeferredInsert();
115
0
}
116
117
/************************************************************************/
118
/*                        GetLayerDefnInternal()                        */
119
/************************************************************************/
120
121
OGRFeatureDefn *
122
OGRAmigoCloudTableLayer::GetLayerDefnInternal(CPL_UNUSED json_object *poObjIn)
123
0
{
124
0
    if (poFeatureDefn != nullptr)
125
0
    {
126
0
        return poFeatureDefn;
127
0
    }
128
129
0
    osBaseSQL.Printf("SELECT * FROM %s",
130
0
                     OGRAMIGOCLOUDEscapeIdentifier(osTableName).c_str());
131
0
    EstablishLayerDefn(osTableName, nullptr);
132
0
    osBaseSQL = "";
133
134
0
    if (!osFIDColName.empty())
135
0
    {
136
0
        CPLString sql;
137
0
        sql.Printf("SELECT %s FROM %s",
138
0
                   OGRAMIGOCLOUDEscapeIdentifier(osFIDColName).c_str(),
139
0
                   OGRAMIGOCLOUDEscapeIdentifier(osTableName).c_str());
140
0
        json_object *poObj = poDS->RunSQL(sql);
141
0
        if (poObj != nullptr && json_object_get_type(poObj) == json_type_object)
142
0
        {
143
0
            json_object *poRows = CPL_json_object_object_get(poObj, "data");
144
145
0
            if (poRows != nullptr &&
146
0
                json_object_get_type(poRows) == json_type_array)
147
0
            {
148
0
                mFIDs.clear();
149
0
                const auto nLength = json_object_array_length(poRows);
150
0
                for (auto i = decltype(nLength){0}; i < nLength; i++)
151
0
                {
152
0
                    json_object *obj = json_object_array_get_idx(poRows, i);
153
154
0
                    json_object_iter it;
155
0
                    it.key = nullptr;
156
0
                    it.val = nullptr;
157
0
                    it.entry = nullptr;
158
0
                    json_object_object_foreachC(obj, it)
159
0
                    {
160
0
                        const char *pszColName = it.key;
161
0
                        if (it.val != nullptr)
162
0
                        {
163
0
                            if (EQUAL(pszColName, osFIDColName.c_str()))
164
0
                            {
165
0
                                std::string amigo_id =
166
0
                                    json_object_get_string(it.val);
167
0
                                OGRAmigoCloudFID aFID(amigo_id, iNext);
168
0
                                mFIDs[aFID.iFID] = aFID;
169
0
                            }
170
0
                        }
171
0
                    }
172
0
                }
173
0
            }
174
0
            json_object_put(poObj);
175
0
        }
176
0
    }
177
178
0
    if (!osFIDColName.empty())
179
0
    {
180
0
        osBaseSQL = "SELECT ";
181
0
        osBaseSQL += OGRAMIGOCLOUDEscapeIdentifier(osFIDColName);
182
0
    }
183
0
    for (int i = 0; i < poFeatureDefn->GetGeomFieldCount(); i++)
184
0
    {
185
0
        if (osBaseSQL.empty())
186
0
            osBaseSQL = "SELECT ";
187
0
        else
188
0
            osBaseSQL += ", ";
189
0
        osBaseSQL += OGRAMIGOCLOUDEscapeIdentifier(
190
0
            poFeatureDefn->GetGeomFieldDefn(i)->GetNameRef());
191
0
    }
192
0
    for (int i = 0; i < poFeatureDefn->GetFieldCount(); i++)
193
0
    {
194
0
        if (osBaseSQL.empty())
195
0
            osBaseSQL = "SELECT ";
196
0
        else
197
0
            osBaseSQL += ", ";
198
0
        osBaseSQL += OGRAMIGOCLOUDEscapeIdentifier(
199
0
            poFeatureDefn->GetFieldDefn(i)->GetNameRef());
200
0
    }
201
0
    if (osBaseSQL.empty())
202
0
        osBaseSQL = "SELECT *";
203
0
    osBaseSQL += " FROM ";
204
0
    osBaseSQL += OGRAMIGOCLOUDEscapeIdentifier(osTableName);
205
206
0
    osSELECTWithoutWHERE = osBaseSQL;
207
208
0
    return poFeatureDefn;
209
0
}
210
211
/************************************************************************/
212
/*                          FetchNewFeatures()                          */
213
/************************************************************************/
214
215
json_object *OGRAmigoCloudTableLayer::FetchNewFeatures(GIntBig iNextIn)
216
0
{
217
0
    if (!osFIDColName.empty())
218
0
    {
219
0
        CPLString osSQL;
220
221
0
        if (!osWHERE.empty())
222
0
        {
223
0
            osSQL.Printf("%s WHERE %s ", osSELECTWithoutWHERE.c_str(),
224
0
                         (!osWHERE.empty()) ? CPLSPrintf("%s", osWHERE.c_str())
225
0
                                            : "");
226
0
        }
227
0
        else
228
0
        {
229
0
            osSQL.Printf("%s", osSELECTWithoutWHERE.c_str());
230
0
        }
231
232
0
        if (osSQL.ifind("SELECT") != std::string::npos &&
233
0
            osSQL.ifind(" LIMIT ") == std::string::npos)
234
0
        {
235
0
            osSQL += " LIMIT ";
236
0
            osSQL += CPLSPrintf("%d", GetFeaturesToFetch());
237
0
            osSQL += " OFFSET ";
238
0
            osSQL += CPLSPrintf(CPL_FRMT_GIB, iNextIn);
239
0
        }
240
0
        return poDS->RunSQL(osSQL);
241
0
    }
242
0
    else
243
0
        return OGRAmigoCloudLayer::FetchNewFeatures(iNextIn);
244
0
}
245
246
/************************************************************************/
247
/*                         GetNextRawFeature()                          */
248
/************************************************************************/
249
250
OGRFeature *OGRAmigoCloudTableLayer::GetNextRawFeature()
251
0
{
252
0
    if (bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE)
253
0
        return nullptr;
254
0
    FlushDeferredInsert();
255
0
    return OGRAmigoCloudLayer::GetNextRawFeature();
256
0
}
257
258
/************************************************************************/
259
/*                         SetAttributeFilter()                         */
260
/************************************************************************/
261
262
OGRErr OGRAmigoCloudTableLayer::SetAttributeFilter(const char *pszQuery)
263
264
0
{
265
0
    GetLayerDefn();
266
267
0
    if (pszQuery == nullptr)
268
0
        osQuery = "";
269
0
    else
270
0
    {
271
0
        osQuery = "(";
272
0
        osQuery += pszQuery;
273
0
        osQuery += ")";
274
0
    }
275
276
0
    BuildWhere();
277
278
0
    ResetReading();
279
280
0
    return OGRERR_NONE;
281
0
}
282
283
/************************************************************************/
284
/*                         ISetSpatialFilter()                          */
285
/************************************************************************/
286
287
OGRErr OGRAmigoCloudTableLayer::ISetSpatialFilter(int iGeomField,
288
                                                  const OGRGeometry *poGeomIn)
289
290
0
{
291
0
    m_iGeomFieldFilter = iGeomField;
292
293
0
    if (InstallFilter(poGeomIn))
294
0
    {
295
0
        BuildWhere();
296
297
0
        ResetReading();
298
0
    }
299
0
    return OGRERR_NONE;
300
0
}
301
302
/************************************************************************/
303
/*                        FlushDeferredInsert()                         */
304
/************************************************************************/
305
306
void OGRAmigoCloudTableLayer::FlushDeferredInsert()
307
308
0
{
309
0
    if (vsDeferredInsertChangesets.empty())
310
0
        return;
311
312
0
    std::stringstream url;
313
0
    url << std::string(poDS->GetAPIURL())
314
0
        << "/users/0/projects/" + std::string(poDS->GetProjectId()) +
315
0
               "/datasets/" + osDatasetId + "/submit_change";
316
317
0
    std::stringstream query;
318
319
0
    query << "{\"type\":\"DML\",\"entity\":\"" << osTableName << "\",";
320
0
    query << "\"parent\":null,\"action\":\"INSERT\",\"data\":[";
321
322
0
    int counter = 0;
323
0
    for (size_t i = 0; i < vsDeferredInsertChangesets.size(); i++)
324
0
    {
325
0
        if (counter > 0)
326
0
            query << ",";
327
0
        query << vsDeferredInsertChangesets[i].c_str();
328
0
        counter++;
329
0
    }
330
0
    query << "]}";
331
332
0
    std::stringstream changeset;
333
0
    changeset << "{\"change\": \"" << OGRAMIGOCLOUDJsonEncode(query.str())
334
0
              << "\"}";
335
336
0
    json_object *poObj =
337
0
        poDS->RunPOST(url.str().c_str(), changeset.str().c_str());
338
0
    if (poObj != nullptr)
339
0
        json_object_put(poObj);
340
341
0
    vsDeferredInsertChangesets.clear();
342
0
    nNextFID = -1;
343
0
}
344
345
/************************************************************************/
346
/*                            CreateField()                             */
347
/************************************************************************/
348
349
OGRErr OGRAmigoCloudTableLayer::CreateField(const OGRFieldDefn *poFieldIn,
350
                                            CPL_UNUSED int bApproxOK)
351
0
{
352
0
    GetLayerDefn();
353
354
0
    if (!poDS->IsReadWrite())
355
0
    {
356
0
        CPLError(CE_Failure, CPLE_AppDefined,
357
0
                 "Operation not available in read-only mode");
358
0
        return OGRERR_FAILURE;
359
0
    }
360
361
0
    OGRFieldDefn oField(poFieldIn);
362
    /* -------------------------------------------------------------------- */
363
    /*      Create the new field.                                           */
364
    /* -------------------------------------------------------------------- */
365
366
0
    if (!bDeferredCreation)
367
0
    {
368
0
        CPLString osSQL;
369
0
        osSQL.Printf("ALTER TABLE %s ADD COLUMN %s %s",
370
0
                     OGRAMIGOCLOUDEscapeIdentifier(osTableName).c_str(),
371
0
                     OGRAMIGOCLOUDEscapeIdentifier(oField.GetNameRef()).c_str(),
372
0
                     OGRPGCommonLayerGetType(oField, false, true).c_str());
373
0
        if (!oField.IsNullable())
374
0
            osSQL += " NOT NULL";
375
0
        if (oField.GetDefault() != nullptr && !oField.IsDefaultDriverSpecific())
376
0
        {
377
0
            osSQL += " DEFAULT ";
378
0
            osSQL += OGRPGCommonLayerGetPGDefault(&oField);
379
0
        }
380
381
0
        json_object *poObj = poDS->RunSQL(osSQL);
382
0
        if (poObj == nullptr)
383
0
            return OGRERR_FAILURE;
384
0
        json_object_put(poObj);
385
0
    }
386
387
0
    poFeatureDefn->AddFieldDefn(&oField);
388
389
0
    return OGRERR_NONE;
390
0
}
391
392
/************************************************************************/
393
/*                           ICreateFeature()                           */
394
/************************************************************************/
395
396
OGRErr OGRAmigoCloudTableLayer::ICreateFeature(OGRFeature *poFeature)
397
398
0
{
399
0
    if (bDeferredCreation)
400
0
    {
401
0
        if (RunDeferredCreationIfNecessary() != OGRERR_NONE)
402
0
            return OGRERR_FAILURE;
403
0
    }
404
405
0
    GetLayerDefn();
406
407
0
    if (!poDS->IsReadWrite())
408
0
    {
409
0
        CPLError(CE_Failure, CPLE_AppDefined,
410
0
                 "Operation not available in read-only mode");
411
0
        return OGRERR_FAILURE;
412
0
    }
413
414
0
    std::stringstream record;
415
416
0
    record << "{\"new\":{";
417
418
0
    int counter = 0;
419
420
    // Add geometry field
421
0
    for (int i = 0; i < poFeatureDefn->GetGeomFieldCount(); i++)
422
0
    {
423
0
        const OGRGeometry *poGeom = poFeature->GetGeomFieldRef(i);
424
0
        if (poGeom == nullptr)
425
0
            continue;
426
427
0
        record << "\""
428
0
               << OGRAMIGOCLOUDJsonEncode(
429
0
                      poFeatureDefn->GetGeomFieldDefn(i)->GetNameRef())
430
0
               << "\":";
431
432
0
        OGRAmigoCloudGeomFieldDefn *poGeomFieldDefn =
433
0
            cpl::down_cast<OGRAmigoCloudGeomFieldDefn *>(
434
0
                poFeatureDefn->GetGeomFieldDefn(i));
435
0
        int nSRID = poGeomFieldDefn->nSRID;
436
0
        if (nSRID == 0)
437
0
            nSRID = 4326;
438
0
        char *pszEWKB = nullptr;
439
0
        if (wkbFlatten(poGeom->getGeometryType()) == wkbPolygon &&
440
0
            wkbFlatten(GetGeomType()) == wkbMultiPolygon)
441
0
        {
442
0
            OGRMultiPolygon *poNewGeom = new OGRMultiPolygon();
443
0
            poNewGeom->addGeometry(poGeom);
444
0
            pszEWKB = OGRGeometryToHexEWKB(poNewGeom, nSRID, 2, 1);
445
0
            delete poNewGeom;
446
0
        }
447
0
        else
448
449
0
            pszEWKB = OGRGeometryToHexEWKB(poGeom, nSRID, 2, 1);
450
0
        record << "\"" << pszEWKB << "\"";
451
0
        CPLFree(pszEWKB);
452
453
0
        counter++;
454
0
    }
455
456
0
    std::string amigo_id_value;
457
458
    // Add non-geometry field
459
0
    for (int i = 0; i < poFeatureDefn->GetFieldCount(); i++)
460
0
    {
461
0
        std::string name = poFeatureDefn->GetFieldDefn(i)->GetNameRef();
462
0
        std::string value = poFeature->GetFieldAsString(i);
463
464
0
        if (name == "amigo_id")
465
0
        {
466
0
            amigo_id_value = std::move(value);
467
0
            continue;
468
0
        }
469
0
        if (!poFeature->IsFieldSet(i))
470
0
            continue;
471
472
0
        if (counter > 0)
473
0
            record << ",";
474
475
0
        record << OGRAMIGOCLOUDEscapeIdentifier(name.c_str()) << ":";
476
477
0
        if (!poFeature->IsFieldNull(i))
478
0
        {
479
0
            OGRFieldType eType = poFeatureDefn->GetFieldDefn(i)->GetType();
480
0
            if (eType == OFTString || eType == OFTDateTime ||
481
0
                eType == OFTDate || eType == OFTTime)
482
0
            {
483
0
                record << "\"" << OGRAMIGOCLOUDJsonEncode(value.c_str())
484
0
                       << "\"";
485
0
            }
486
0
            else
487
0
                record << OGRAMIGOCLOUDJsonEncode(value.c_str());
488
0
        }
489
0
        else
490
0
            record << "null";
491
492
0
        counter++;
493
0
    }
494
495
0
    record << "},";
496
497
0
    if (!amigo_id_value.empty())
498
0
    {
499
0
        record << "\"amigo_id\":\"" << amigo_id_value << "\"";
500
0
    }
501
0
    else
502
0
    {
503
0
        record << "\"amigo_id\":null";
504
0
    }
505
506
0
    record << "}";
507
508
0
    vsDeferredInsertChangesets.push_back(record.str());
509
510
0
    return OGRERR_NONE;
511
0
}
512
513
/************************************************************************/
514
/*                            ISetFeature()                             */
515
/************************************************************************/
516
517
OGRErr OGRAmigoCloudTableLayer::ISetFeature(OGRFeature *poFeature)
518
519
0
{
520
0
    OGRErr eRet = OGRERR_FAILURE;
521
522
0
    if (bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE)
523
0
        return OGRERR_FAILURE;
524
0
    FlushDeferredInsert();
525
526
0
    GetLayerDefn();
527
528
0
    if (!poDS->IsReadWrite())
529
0
    {
530
0
        CPLError(CE_Failure, CPLE_AppDefined,
531
0
                 "Operation not available in read-only mode");
532
0
        return OGRERR_FAILURE;
533
0
    }
534
535
0
    if (poFeature->GetFID() == OGRNullFID)
536
0
    {
537
0
        CPLError(CE_Failure, CPLE_AppDefined,
538
0
                 "FID required on features given to SetFeature().");
539
0
        return OGRERR_FAILURE;
540
0
    }
541
542
0
    const auto it = mFIDs.find(poFeature->GetFID());
543
0
    if (it != mFIDs.end())
544
0
    {
545
0
        const OGRAmigoCloudFID &aFID = it->second;
546
547
0
        CPLString osSQL;
548
0
        osSQL.Printf("UPDATE %s SET ",
549
0
                     OGRAMIGOCLOUDEscapeIdentifier(osTableName).c_str());
550
0
        bool bMustComma = false;
551
0
        for (int i = 0; i < poFeatureDefn->GetFieldCount(); i++)
552
0
        {
553
0
            if (!poFeature->IsFieldSet(i))
554
0
                continue;
555
556
0
            if (bMustComma)
557
0
                osSQL += ", ";
558
0
            else
559
0
                bMustComma = true;
560
561
0
            osSQL += OGRAMIGOCLOUDEscapeIdentifier(
562
0
                poFeatureDefn->GetFieldDefn(i)->GetNameRef());
563
0
            osSQL += " = ";
564
565
0
            if (poFeature->IsFieldNull(i))
566
0
            {
567
0
                osSQL += "NULL";
568
0
            }
569
0
            else
570
0
            {
571
0
                OGRFieldType eType = poFeatureDefn->GetFieldDefn(i)->GetType();
572
0
                if (eType == OFTString || eType == OFTDateTime ||
573
0
                    eType == OFTDate || eType == OFTTime)
574
0
                {
575
0
                    osSQL += "'";
576
0
                    osSQL +=
577
0
                        OGRAMIGOCLOUDJsonEncode(poFeature->GetFieldAsString(i));
578
0
                    osSQL += "'";
579
0
                }
580
0
                else if ((eType == OFTInteger || eType == OFTInteger64) &&
581
0
                         poFeatureDefn->GetFieldDefn(i)->GetSubType() ==
582
0
                             OFSTBoolean)
583
0
                {
584
0
                    osSQL += poFeature->GetFieldAsInteger(i) ? "'t'" : "'f'";
585
0
                }
586
0
                else
587
0
                    osSQL += poFeature->GetFieldAsString(i);
588
0
            }
589
0
        }
590
591
0
        for (int i = 0; i < poFeatureDefn->GetGeomFieldCount(); i++)
592
0
        {
593
0
            if (bMustComma)
594
0
                osSQL += ", ";
595
0
            else
596
0
                bMustComma = true;
597
598
0
            osSQL += OGRAMIGOCLOUDEscapeIdentifier(
599
0
                poFeatureDefn->GetGeomFieldDefn(i)->GetNameRef());
600
0
            osSQL += " = ";
601
602
0
            OGRGeometry *poGeom = poFeature->GetGeomFieldRef(i);
603
0
            if (poGeom == nullptr)
604
0
            {
605
0
                osSQL += "NULL";
606
0
            }
607
0
            else
608
0
            {
609
0
                OGRAmigoCloudGeomFieldDefn *poGeomFieldDefn =
610
0
                    cpl::down_cast<OGRAmigoCloudGeomFieldDefn *>(
611
0
                        poFeatureDefn->GetGeomFieldDefn(i));
612
0
                int nSRID = poGeomFieldDefn->nSRID;
613
0
                if (nSRID == 0)
614
0
                    nSRID = 4326;
615
0
                char *pszEWKB = OGRGeometryToHexEWKB(poGeom, nSRID, 2, 1);
616
0
                osSQL += "'";
617
0
                osSQL += pszEWKB;
618
0
                osSQL += "'";
619
0
                CPLFree(pszEWKB);
620
0
            }
621
0
        }
622
623
0
        if (!bMustComma)  // nothing to do
624
0
            return OGRERR_NONE;
625
626
0
        osSQL += CPLSPrintf(" WHERE %s = '%s'",
627
0
                            OGRAMIGOCLOUDEscapeIdentifier(osFIDColName).c_str(),
628
0
                            aFID.osAmigoId.c_str());
629
630
0
        std::stringstream changeset;
631
0
        changeset << "{\"query\": \"" << OGRAMIGOCLOUDJsonEncode(osSQL)
632
0
                  << "\"}";
633
0
        std::stringstream url;
634
0
        url << std::string(poDS->GetAPIURL())
635
0
            << "/users/0/projects/" + std::string(poDS->GetProjectId()) +
636
0
                   "/sql";
637
0
        json_object *poObj =
638
0
            poDS->RunPOST(url.str().c_str(), changeset.str().c_str());
639
640
0
        if (poObj != nullptr)
641
0
        {
642
0
            json_object *poTotalRows =
643
0
                CPL_json_object_object_get(poObj, "total_rows");
644
0
            if (poTotalRows != nullptr &&
645
0
                json_object_get_type(poTotalRows) == json_type_int)
646
0
            {
647
0
                int nTotalRows = json_object_get_int(poTotalRows);
648
0
                if (nTotalRows > 0)
649
0
                {
650
0
                    eRet = OGRERR_NONE;
651
0
                }
652
0
                else
653
0
                    eRet = OGRERR_NON_EXISTING_FEATURE;
654
0
            }
655
0
            json_object_put(poObj);
656
0
        }
657
0
    }
658
0
    return eRet;
659
0
}
660
661
/************************************************************************/
662
/*                           DeleteFeature()                            */
663
/************************************************************************/
664
665
OGRErr OGRAmigoCloudTableLayer::DeleteFeature(GIntBig nFID)
666
667
0
{
668
0
    OGRErr eRet = OGRERR_FAILURE;
669
670
0
    if (bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE)
671
0
        return OGRERR_FAILURE;
672
0
    FlushDeferredInsert();
673
674
0
    GetLayerDefn();
675
676
0
    if (!poDS->IsReadWrite())
677
0
    {
678
0
        CPLError(CE_Failure, CPLE_AppDefined,
679
0
                 "Operation not available in read-only mode");
680
0
        return OGRERR_FAILURE;
681
0
    }
682
683
0
    if (osFIDColName.empty())
684
0
        return OGRERR_FAILURE;
685
686
0
    const auto it = mFIDs.find(nFID);
687
0
    if (it != mFIDs.end())
688
0
    {
689
0
        const OGRAmigoCloudFID &aFID = it->second;
690
691
0
        CPLString osSQL;
692
0
        osSQL.Printf("DELETE FROM %s WHERE %s = '%s'",
693
0
                     OGRAMIGOCLOUDEscapeIdentifier(osTableName).c_str(),
694
0
                     OGRAMIGOCLOUDEscapeIdentifier(osFIDColName).c_str(),
695
0
                     aFID.osAmigoId.c_str());
696
697
0
        std::stringstream changeset;
698
0
        changeset << "{\"query\": \"" << OGRAMIGOCLOUDJsonEncode(osSQL)
699
0
                  << "\"}";
700
0
        std::stringstream url;
701
0
        url << std::string(poDS->GetAPIURL())
702
0
            << "/users/0/projects/" + std::string(poDS->GetProjectId()) +
703
0
                   "/sql";
704
0
        json_object *poObj =
705
0
            poDS->RunPOST(url.str().c_str(), changeset.str().c_str());
706
0
        if (poObj != nullptr)
707
0
        {
708
0
            json_object_put(poObj);
709
0
            eRet = OGRERR_NONE;
710
0
        }
711
0
    }
712
0
    return eRet;
713
0
}
714
715
/************************************************************************/
716
/*                             GetSRS_SQL()                             */
717
/************************************************************************/
718
719
CPLString OGRAmigoCloudTableLayer::GetSRS_SQL(const char *pszGeomCol)
720
0
{
721
0
    CPLString osSQL;
722
723
0
    osSQL.Printf("SELECT srid, srtext FROM spatial_ref_sys WHERE srid IN "
724
0
                 "(SELECT Find_SRID('%s', '%s', '%s'))",
725
0
                 OGRAMIGOCLOUDJsonEncode(poDS->GetCurrentSchema()).c_str(),
726
0
                 OGRAMIGOCLOUDJsonEncode(osTableName).c_str(),
727
0
                 OGRAMIGOCLOUDJsonEncode(pszGeomCol).c_str());
728
729
0
    return osSQL;
730
0
}
731
732
/************************************************************************/
733
/*                             BuildWhere()                             */
734
/*                                                                      */
735
/*      Build the WHERE statement appropriate to the current set of     */
736
/*      criteria (spatial and attribute queries).                       */
737
/************************************************************************/
738
739
void OGRAmigoCloudTableLayer::BuildWhere()
740
741
0
{
742
0
    osWHERE = "";
743
744
0
    if (m_poFilterGeom != nullptr && m_iGeomFieldFilter >= 0 &&
745
0
        m_iGeomFieldFilter < poFeatureDefn->GetGeomFieldCount())
746
0
    {
747
0
        OGREnvelope sEnvelope;
748
749
0
        m_poFilterGeom->getEnvelope(&sEnvelope);
750
751
0
        CPLString osGeomColumn(
752
0
            poFeatureDefn->GetGeomFieldDefn(m_iGeomFieldFilter)->GetNameRef());
753
754
0
        char szBox3D_1[128];
755
0
        char szBox3D_2[128];
756
0
        char *pszComma = nullptr;
757
758
0
        CPLsnprintf(szBox3D_1, sizeof(szBox3D_1), "%.17g %.17g", sEnvelope.MinX,
759
0
                    sEnvelope.MinY);
760
0
        while ((pszComma = strchr(szBox3D_1, ',')) != nullptr)
761
0
            *pszComma = '.';
762
0
        CPLsnprintf(szBox3D_2, sizeof(szBox3D_2), "%.17g %.17g", sEnvelope.MaxX,
763
0
                    sEnvelope.MaxY);
764
0
        while ((pszComma = strchr(szBox3D_2, ',')) != nullptr)
765
0
            *pszComma = '.';
766
0
        osWHERE.Printf("(%s && 'BOX3D(%s, %s)'::box3d)",
767
0
                       OGRAMIGOCLOUDEscapeIdentifier(osGeomColumn).c_str(),
768
0
                       szBox3D_1, szBox3D_2);
769
0
    }
770
771
0
    if (!osQuery.empty())
772
0
    {
773
0
        if (!osWHERE.empty())
774
0
            osWHERE += " AND ";
775
0
        osWHERE += osQuery;
776
0
    }
777
778
0
    if (osFIDColName.empty())
779
0
    {
780
0
        osBaseSQL = osSELECTWithoutWHERE;
781
0
        if (!osWHERE.empty())
782
0
        {
783
0
            osBaseSQL += " WHERE ";
784
0
            osBaseSQL += osWHERE;
785
0
        }
786
0
    }
787
0
}
788
789
/************************************************************************/
790
/*                             GetFeature()                             */
791
/************************************************************************/
792
793
OGRFeature *OGRAmigoCloudTableLayer::GetFeature(GIntBig nFeatureId)
794
0
{
795
796
0
    if (bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE)
797
0
        return nullptr;
798
0
    FlushDeferredInsert();
799
800
0
    GetLayerDefn();
801
802
0
    if (osFIDColName.empty())
803
0
        return OGRAmigoCloudLayer::GetFeature(nFeatureId);
804
805
0
    const auto it = mFIDs.find(nFeatureId);
806
0
    if (it != mFIDs.end())
807
0
    {
808
0
        const OGRAmigoCloudFID &aFID = it->second;
809
810
0
        CPLString osSQL = osSELECTWithoutWHERE;
811
0
        osSQL += " WHERE ";
812
0
        osSQL += OGRAMIGOCLOUDEscapeIdentifier(osFIDColName).c_str();
813
0
        osSQL += " = ";
814
0
        osSQL += CPLSPrintf("'%s'", aFID.osAmigoId.c_str());
815
816
0
        json_object *poObj = poDS->RunSQL(osSQL);
817
0
        json_object *poRowObj = OGRAMIGOCLOUDGetSingleRow(poObj);
818
0
        if (poRowObj == nullptr)
819
0
        {
820
0
            if (poObj != nullptr)
821
0
                json_object_put(poObj);
822
0
            return OGRAmigoCloudLayer::GetFeature(nFeatureId);
823
0
        }
824
825
0
        OGRFeature *poFeature = BuildFeature(poRowObj);
826
0
        json_object_put(poObj);
827
828
0
        return poFeature;
829
0
    }
830
0
    return nullptr;
831
0
}
832
833
/************************************************************************/
834
/*                          GetFeatureCount()                           */
835
/************************************************************************/
836
837
GIntBig OGRAmigoCloudTableLayer::GetFeatureCount(int bForce)
838
0
{
839
840
0
    if (bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE)
841
0
        return 0;
842
0
    FlushDeferredInsert();
843
844
0
    GetLayerDefn();
845
846
0
    CPLString osSQL(
847
0
        CPLSPrintf("SELECT COUNT(*) FROM %s",
848
0
                   OGRAMIGOCLOUDEscapeIdentifier(osTableName).c_str()));
849
0
    if (!osWHERE.empty())
850
0
    {
851
0
        osSQL += " WHERE ";
852
0
        osSQL += osWHERE;
853
0
    }
854
855
0
    json_object *poObj = poDS->RunSQL(osSQL);
856
0
    json_object *poRowObj = OGRAMIGOCLOUDGetSingleRow(poObj);
857
0
    if (poRowObj == nullptr)
858
0
    {
859
0
        if (poObj != nullptr)
860
0
            json_object_put(poObj);
861
0
        return OGRAmigoCloudLayer::GetFeatureCount(bForce);
862
0
    }
863
864
0
    json_object *poCount = CPL_json_object_object_get(poRowObj, "count");
865
0
    if (poCount == nullptr || json_object_get_type(poCount) != json_type_int)
866
0
    {
867
0
        json_object_put(poObj);
868
0
        return OGRAmigoCloudLayer::GetFeatureCount(bForce);
869
0
    }
870
871
0
    GIntBig nRet = (GIntBig)json_object_get_int64(poCount);
872
873
0
    json_object_put(poObj);
874
875
0
    return nRet;
876
0
}
877
878
/************************************************************************/
879
/*                            IGetExtent()                              */
880
/*                                                                      */
881
/*      For PostGIS use internal Extend(geometry) function              */
882
/*      in other cases we use standard OGRLayer::GetExtent()            */
883
/************************************************************************/
884
885
OGRErr OGRAmigoCloudTableLayer::IGetExtent(int iGeomField,
886
                                           OGREnvelope *psExtent, bool bForce)
887
0
{
888
0
    CPLString osSQL;
889
890
0
    if (bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE)
891
0
        return OGRERR_FAILURE;
892
0
    FlushDeferredInsert();
893
894
0
    OGRGeomFieldDefn *poGeomFieldDefn =
895
0
        poFeatureDefn->GetGeomFieldDefn(iGeomField);
896
897
    /* Do not take the spatial filter into account */
898
0
    osSQL.Printf(
899
0
        "SELECT ST_Extent(%s) FROM %s",
900
0
        OGRAMIGOCLOUDEscapeIdentifier(poGeomFieldDefn->GetNameRef()).c_str(),
901
0
        OGRAMIGOCLOUDEscapeIdentifier(osTableName).c_str());
902
903
0
    json_object *poObj = poDS->RunSQL(osSQL);
904
0
    json_object *poRowObj = OGRAMIGOCLOUDGetSingleRow(poObj);
905
0
    if (poRowObj != nullptr)
906
0
    {
907
0
        json_object *poExtent =
908
0
            CPL_json_object_object_get(poRowObj, "st_extent");
909
0
        if (poExtent != nullptr &&
910
0
            json_object_get_type(poExtent) == json_type_string)
911
0
        {
912
0
            const char *pszBox = json_object_get_string(poExtent);
913
0
            const char *ptr, *ptrEndParenthesis;
914
0
            char szVals[64 * 6 + 6];
915
916
0
            ptr = strchr(pszBox, '(');
917
0
            if (ptr)
918
0
                ptr++;
919
0
            if (ptr == nullptr ||
920
0
                (ptrEndParenthesis = strchr(ptr, ')')) == nullptr ||
921
0
                ptrEndParenthesis - ptr > (int)(sizeof(szVals) - 1))
922
0
            {
923
0
                CPLError(CE_Failure, CPLE_IllegalArg,
924
0
                         "Bad extent representation: '%s'", pszBox);
925
926
0
                json_object_put(poObj);
927
0
                return OGRERR_FAILURE;
928
0
            }
929
930
0
            strncpy(szVals, ptr, ptrEndParenthesis - ptr);
931
0
            szVals[ptrEndParenthesis - ptr] = '\0';
932
933
0
            char **papszTokens =
934
0
                CSLTokenizeString2(szVals, " ,", CSLT_HONOURSTRINGS);
935
0
            int nTokenCnt = 4;
936
937
0
            if (CSLCount(papszTokens) != nTokenCnt)
938
0
            {
939
0
                CPLError(CE_Failure, CPLE_IllegalArg,
940
0
                         "Bad extent representation: '%s'", pszBox);
941
0
                CSLDestroy(papszTokens);
942
943
0
                json_object_put(poObj);
944
0
                return OGRERR_FAILURE;
945
0
            }
946
947
            // Take X,Y coords
948
            // For PostGIS ver >= 1.0.0 -> Tokens: X1 Y1 X2 Y2 (nTokenCnt = 4)
949
            // For PostGIS ver < 1.0.0 -> Tokens: X1 Y1 Z1 X2 Y2 Z2 (nTokenCnt =
950
            // 6)
951
            // =>   X2 index calculated as nTokenCnt/2
952
            //      Y2 index calculated as nTokenCnt/2+1
953
954
0
            psExtent->MinX = CPLAtof(papszTokens[0]);
955
0
            psExtent->MinY = CPLAtof(papszTokens[1]);
956
0
            psExtent->MaxX = CPLAtof(papszTokens[nTokenCnt / 2]);
957
0
            psExtent->MaxY = CPLAtof(papszTokens[nTokenCnt / 2 + 1]);
958
959
0
            CSLDestroy(papszTokens);
960
961
0
            json_object_put(poObj);
962
0
            return OGRERR_NONE;
963
0
        }
964
0
    }
965
966
0
    if (poObj != nullptr)
967
0
        json_object_put(poObj);
968
969
0
    return OGRLayer::IGetExtent(iGeomField, psExtent, bForce);
970
0
}
971
972
/************************************************************************/
973
/*                           TestCapability()                           */
974
/************************************************************************/
975
976
int OGRAmigoCloudTableLayer::TestCapability(const char *pszCap) const
977
978
0
{
979
0
    if (EQUAL(pszCap, OLCFastFeatureCount))
980
0
        return TRUE;
981
0
    if (EQUAL(pszCap, OLCFastGetExtent))
982
0
        return TRUE;
983
0
    if (EQUAL(pszCap, OLCRandomRead))
984
0
    {
985
0
        GetLayerDefn();
986
0
        return !osFIDColName.empty();
987
0
    }
988
989
0
    if (EQUAL(pszCap, OLCSequentialWrite) || EQUAL(pszCap, OLCRandomWrite) ||
990
0
        EQUAL(pszCap, OLCDeleteFeature) || EQUAL(pszCap, ODsCCreateLayer) ||
991
0
        EQUAL(pszCap, ODsCDeleteLayer))
992
0
    {
993
0
        return poDS->IsReadWrite();
994
0
    }
995
996
0
    return OGRAmigoCloudLayer::TestCapability(pszCap);
997
0
}
998
999
/************************************************************************/
1000
/*                        SetDeferredCreation()                         */
1001
/************************************************************************/
1002
1003
void OGRAmigoCloudTableLayer::SetDeferredCreation(OGRwkbGeometryType eGType,
1004
                                                  OGRSpatialReference *poSRS,
1005
                                                  int bGeomNullable)
1006
0
{
1007
0
    bDeferredCreation = TRUE;
1008
0
    nNextFID = 1;
1009
0
    CPLAssert(poFeatureDefn == nullptr);
1010
0
    poFeatureDefn = new OGRFeatureDefn(osTableName);
1011
0
    poFeatureDefn->Reference();
1012
0
    poFeatureDefn->SetGeomType(wkbNone);
1013
0
    if (eGType == wkbPolygon)
1014
0
        eGType = wkbMultiPolygon;
1015
0
    else if (eGType == wkbPolygon25D)
1016
0
        eGType = wkbMultiPolygon25D;
1017
0
    if (eGType != wkbNone)
1018
0
    {
1019
0
        auto poFieldDefn = std::make_unique<OGRAmigoCloudGeomFieldDefn>(
1020
0
            "wkb_geometry", eGType);
1021
0
        poFieldDefn->SetNullable(bGeomNullable);
1022
0
        if (poSRS != nullptr)
1023
0
        {
1024
0
            poFieldDefn->nSRID = poDS->FetchSRSId(poSRS);
1025
0
            poFieldDefn->SetSpatialRef(poSRS);
1026
0
        }
1027
0
        poFeatureDefn->AddGeomFieldDefn(std::move(poFieldDefn));
1028
0
    }
1029
1030
0
    osBaseSQL.Printf("SELECT * FROM %s",
1031
0
                     OGRAMIGOCLOUDEscapeIdentifier(osTableName).c_str());
1032
0
}
1033
1034
CPLString OGRAmigoCloudTableLayer::GetAmigoCloudType(const OGRFieldDefn &oField)
1035
0
{
1036
0
    char szFieldType[256];
1037
1038
    /* -------------------------------------------------------------------- */
1039
    /*      AmigoCloud supported types.                                   */
1040
    /* -------------------------------------------------------------------- */
1041
0
    if (oField.GetType() == OFTInteger)
1042
0
    {
1043
0
        strcpy(szFieldType, "integer");
1044
0
    }
1045
0
    else if (oField.GetType() == OFTInteger64)
1046
0
    {
1047
0
        strcpy(szFieldType, "bigint");
1048
0
    }
1049
0
    else if (oField.GetType() == OFTReal)
1050
0
    {
1051
0
        strcpy(szFieldType, "float");
1052
0
    }
1053
0
    else if (oField.GetType() == OFTString)
1054
0
    {
1055
0
        strcpy(szFieldType, "string");
1056
0
    }
1057
0
    else if (oField.GetType() == OFTDate)
1058
0
    {
1059
0
        strcpy(szFieldType, "date");
1060
0
    }
1061
0
    else if (oField.GetType() == OFTTime)
1062
0
    {
1063
0
        strcpy(szFieldType, "time");
1064
0
    }
1065
0
    else if (oField.GetType() == OFTDateTime)
1066
0
    {
1067
0
        strcpy(szFieldType, "datetime");
1068
0
    }
1069
0
    else
1070
0
    {
1071
0
        CPLError(CE_Failure, CPLE_NotSupported,
1072
0
                 "Can't create field %s with type %s on PostgreSQL layers.",
1073
0
                 oField.GetNameRef(),
1074
0
                 OGRFieldDefn::GetFieldTypeName(oField.GetType()));
1075
0
        strcpy(szFieldType, "");
1076
0
    }
1077
1078
0
    return szFieldType;
1079
0
}
1080
1081
bool OGRAmigoCloudTableLayer::IsDatasetExists()
1082
0
{
1083
0
    std::stringstream url;
1084
0
    url << std::string(poDS->GetAPIURL())
1085
0
        << "/users/0/projects/" + std::string(poDS->GetProjectId()) +
1086
0
               "/datasets/" + osDatasetId;
1087
0
    json_object *result = poDS->RunGET(url.str().c_str());
1088
0
    if (result == nullptr)
1089
0
        return false;
1090
1091
0
    {
1092
0
        int type = json_object_get_type(result);
1093
0
        if (type == json_type_object)
1094
0
        {
1095
0
            json_object *poId = CPL_json_object_object_get(result, "id");
1096
0
            if (poId != nullptr)
1097
0
            {
1098
0
                json_object_put(result);
1099
0
                return true;
1100
0
            }
1101
0
        }
1102
0
        json_object_put(result);
1103
0
    }
1104
1105
    // Sleep 3 sec
1106
0
    CPLSleep(3);
1107
1108
0
    return false;
1109
0
}
1110
1111
/************************************************************************/
1112
/*                   RunDeferredCreationIfNecessary()                   */
1113
/************************************************************************/
1114
1115
OGRErr OGRAmigoCloudTableLayer::RunDeferredCreationIfNecessary()
1116
0
{
1117
0
    if (!bDeferredCreation)
1118
0
        return OGRERR_NONE;
1119
0
    bDeferredCreation = FALSE;
1120
0
    std::stringstream json;
1121
0
    json << "{ \"name\":\"" << osDatasetId << "\",";
1122
0
    json << "\"schema\": \"[";
1123
0
    int counter = 0;
1124
0
    OGRwkbGeometryType eGType = GetGeomType();
1125
0
    if (eGType != wkbNone)
1126
0
    {
1127
0
        CPLString osGeomType = OGRToOGCGeomType(eGType);
1128
0
        if (wkbHasZ(eGType))
1129
0
            osGeomType += "Z";
1130
1131
0
        OGRAmigoCloudGeomFieldDefn *poFieldDefn =
1132
0
            cpl::down_cast<OGRAmigoCloudGeomFieldDefn *>(
1133
0
                poFeatureDefn->GetGeomFieldDefn(0));
1134
1135
0
        json << "{\\\"name\\\":\\\"" << poFieldDefn->GetNameRef() << "\\\",";
1136
0
        json << "\\\"type\\\":\\\"geometry\\\",";
1137
0
        json << "\\\"geometry_type\\\":\\\"" << osGeomType << "\\\",";
1138
1139
0
        if (!poFieldDefn->IsNullable())
1140
0
            json << "\\\"nullable\\\":false,";
1141
0
        else
1142
0
            json << "\\\"nullable\\\":true,";
1143
1144
0
        json << "\\\"visible\\\": true}";
1145
1146
0
        counter++;
1147
0
    }
1148
1149
0
    for (int i = 0; i < poFeatureDefn->GetFieldCount(); i++)
1150
0
    {
1151
0
        OGRFieldDefn *poFieldDefn = poFeatureDefn->GetFieldDefn(i);
1152
0
        if (strcmp(poFieldDefn->GetNameRef(), osFIDColName) != 0)
1153
0
        {
1154
0
            if (counter > 0)
1155
0
                json << ",";
1156
1157
0
            json << "{\\\"name\\\":\\\"" << poFieldDefn->GetNameRef()
1158
0
                 << "\\\",";
1159
0
            json << "\\\"type\\\":\\\"" << GetAmigoCloudType(*poFieldDefn)
1160
0
                 << "\\\",";
1161
0
            if (!poFieldDefn->IsNullable())
1162
0
                json << "\\\"nullable\\\":false,";
1163
0
            else
1164
0
                json << "\\\"nullable\\\":true,";
1165
1166
0
            if (poFieldDefn->GetDefault() != nullptr &&
1167
0
                !poFieldDefn->IsDefaultDriverSpecific())
1168
0
            {
1169
0
                json << "\\\"default\\\":\\\"" << poFieldDefn->GetDefault()
1170
0
                     << "\\\",";
1171
0
            }
1172
0
            json << "\\\"visible\\\": true}";
1173
0
            counter++;
1174
0
        }
1175
0
    }
1176
1177
0
    json << " ] \" }";
1178
1179
0
    std::stringstream url;
1180
0
    url << std::string(poDS->GetAPIURL())
1181
0
        << "/users/0/projects/" + std::string(poDS->GetProjectId()) +
1182
0
               "/datasets/create";
1183
1184
0
    json_object *result = poDS->RunPOST(url.str().c_str(), json.str().c_str());
1185
0
    if (result != nullptr)
1186
0
    {
1187
0
        if (json_object_get_type(result) == json_type_object)
1188
0
        {
1189
0
            json_object *poName = CPL_json_object_object_get(result, "name");
1190
0
            if (poName != nullptr)
1191
0
            {
1192
0
                osName = json_object_to_json_string(poName);
1193
0
            }
1194
1195
0
            json_object *poId = CPL_json_object_object_get(result, "id");
1196
0
            if (poId != nullptr)
1197
0
            {
1198
0
                osTableName =
1199
0
                    CPLString("dataset_") + json_object_to_json_string(poId);
1200
0
                osDatasetId = json_object_to_json_string(poId);
1201
0
                int retry = 10;
1202
0
                while (!IsDatasetExists() && retry >= 0)
1203
0
                {
1204
0
                    retry--;
1205
0
                }
1206
0
                json_object_put(result);
1207
0
                return OGRERR_NONE;
1208
0
            }
1209
0
        }
1210
0
    }
1211
0
    return OGRERR_FAILURE;
1212
0
}