1 /******************************************************************************
2  *
3  * Project:  OpenGIS Simple Features Reference Implementation
4  * Purpose:  Implements OGRPGDumpLayer class
5  * Author:   Even Rouault, <even dot rouault at spatialys.com>
6  *
7  ******************************************************************************
8  * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  ****************************************************************************/
28 
29 #include "ogr_pgdump.h"
30 #include "cpl_conv.h"
31 #include "cpl_string.h"
32 #include "ogr_p.h"
33 
34 CPL_CVSID("$Id: ogrpgdumplayer.cpp 379fc8667418dc54e864d828ea35be513e69abc9 2021-04-03 21:58:21 +0200 Even Rouault $")
35 //
36 static CPLString OGRPGDumpEscapeStringList(
37     char** papszItems, bool bForInsertOrUpdate,
38     OGRPGCommonEscapeStringCbk pfnEscapeString,
39     void* userdata );
40 
OGRPGDumpEscapeStringWithUserData(CPL_UNUSED void * user_data,const char * pszStrValue,int nMaxLength,CPL_UNUSED const char * pszLayerName,const char * pszFieldName)41 static CPLString OGRPGDumpEscapeStringWithUserData(
42     CPL_UNUSED void* user_data,
43     const char* pszStrValue, int nMaxLength,
44     CPL_UNUSED const char* pszLayerName,
45     const char* pszFieldName )
46 {
47     return OGRPGDumpEscapeString(pszStrValue, nMaxLength, pszFieldName);
48 }
49 
50 /************************************************************************/
51 /*                        OGRPGDumpLayer()                              */
52 /************************************************************************/
53 
OGRPGDumpLayer(OGRPGDumpDataSource * poDSIn,const char * pszSchemaNameIn,const char * pszTableName,const char * pszFIDColumnIn,int bWriteAsHexIn,int bCreateTableIn)54 OGRPGDumpLayer::OGRPGDumpLayer( OGRPGDumpDataSource* poDSIn,
55                                 const char* pszSchemaNameIn,
56                                 const char* pszTableName,
57                                 const char *pszFIDColumnIn,
58                                 int bWriteAsHexIn,
59                                 int bCreateTableIn ) :
60     pszSchemaName(CPLStrdup(pszSchemaNameIn)),
61     pszSqlTableName(CPLStrdup(
62         CPLString().Printf("%s.%s",
63                            OGRPGDumpEscapeColumnName(pszSchemaName).c_str(),
64                            OGRPGDumpEscapeColumnName(pszTableName).c_str()))),
65     pszFIDColumn(CPLStrdup(pszFIDColumnIn)),
66     poFeatureDefn(new OGRFeatureDefn(pszTableName)),
67     poDS(poDSIn),
68     bWriteAsHex(CPL_TO_BOOL(bWriteAsHexIn)),
69     bCreateTable(bCreateTableIn)
70 {
71     SetDescription( poFeatureDefn->GetName() );
72     poFeatureDefn->SetGeomType(wkbNone);
73     poFeatureDefn->Reference();
74 }
75 
76 /************************************************************************/
77 /*                          ~OGRPGDumpLayer()                           */
78 /************************************************************************/
79 
~OGRPGDumpLayer()80 OGRPGDumpLayer::~OGRPGDumpLayer()
81 {
82     EndCopy();
83     UpdateSequenceIfNeeded();
84 
85     poFeatureDefn->Release();
86     CPLFree(pszSchemaName);
87     CPLFree(pszSqlTableName);
88     CPLFree(pszFIDColumn);
89     CSLDestroy(papszOverrideColumnTypes);
90 }
91 
92 /************************************************************************/
93 /*                           GetNextFeature()                           */
94 /************************************************************************/
95 
GetNextFeature()96 OGRFeature *OGRPGDumpLayer::GetNextFeature()
97 {
98     CPLError(CE_Failure, CPLE_NotSupported, "PGDump driver is write only");
99     return nullptr;
100 }
101 
102 /************************************************************************/
103 /*                           GetNextFeature()                           */
104 /************************************************************************/
105 
TestCapability(const char * pszCap)106 int OGRPGDumpLayer::TestCapability( const char * pszCap )
107 {
108     if( EQUAL(pszCap,OLCSequentialWrite) ||
109         EQUAL(pszCap,OLCCreateField) ||
110         EQUAL(pszCap,OLCCreateGeomField) ||
111         EQUAL(pszCap,OLCCurveGeometries) ||
112         EQUAL(pszCap,OLCMeasuredGeometries) )
113         return TRUE;
114     else
115         return FALSE;
116 }
117 
118 /************************************************************************/
119 /*                           GetNextFeature()                           */
120 /************************************************************************/
121 
ICreateFeature(OGRFeature * poFeature)122 OGRErr OGRPGDumpLayer::ICreateFeature( OGRFeature *poFeature )
123 {
124     if( nullptr == poFeature )
125     {
126         CPLError( CE_Failure, CPLE_AppDefined,
127                   "NULL pointer to OGRFeature passed to CreateFeature()." );
128         return OGRERR_FAILURE;
129     }
130 
131     /* In case the FID column has also been created as a regular field */
132     if( iFIDAsRegularColumnIndex >= 0 )
133     {
134         if( poFeature->GetFID() == OGRNullFID )
135         {
136             if( poFeature->IsFieldSetAndNotNull( iFIDAsRegularColumnIndex ) )
137             {
138                 poFeature->SetFID(
139                     poFeature->GetFieldAsInteger64(iFIDAsRegularColumnIndex));
140             }
141         }
142         else
143         {
144             if( !poFeature->IsFieldSetAndNotNull( iFIDAsRegularColumnIndex ) ||
145                 poFeature->GetFieldAsInteger64(iFIDAsRegularColumnIndex) != poFeature->GetFID() )
146             {
147                 CPLError(CE_Failure, CPLE_AppDefined,
148                          "Inconsistent values of FID and field of same name");
149                 return OGRERR_FAILURE;
150             }
151         }
152     }
153 
154     if( !poFeature->Validate((OGR_F_VAL_ALL & ~OGR_F_VAL_WIDTH) |
155                              OGR_F_VAL_ALLOW_DIFFERENT_GEOM_DIM, TRUE ) )
156         return OGRERR_FAILURE;
157 
158     // We avoid testing the config option too often.
159     if( bUseCopy == USE_COPY_UNSET )
160         bUseCopy = CPLTestBool( CPLGetConfigOption( "PG_USE_COPY", "NO") );
161 
162     OGRErr eErr;
163     if( !bUseCopy )
164     {
165         eErr = CreateFeatureViaInsert( poFeature );
166     }
167     else
168     {
169         // If there's a unset field with a default value, then we must use a
170         // specific INSERT statement to avoid unset fields to be bound to NULL.
171         bool bHasDefaultValue = false;
172         const int nFieldCount = poFeatureDefn->GetFieldCount();
173         for( int iField = 0; iField < nFieldCount; iField++ )
174         {
175             if( !poFeature->IsFieldSetAndNotNull( iField ) &&
176                 poFeature->GetFieldDefnRef(iField)->GetDefault() != nullptr )
177             {
178                 bHasDefaultValue = true;
179                 break;
180             }
181         }
182         if( bHasDefaultValue )
183         {
184             EndCopy();
185             eErr = CreateFeatureViaInsert( poFeature );
186         }
187         else
188         {
189             const bool bFIDSet = poFeature->GetFID() != OGRNullFID;
190             if( bCopyActive && bFIDSet != bCopyStatementWithFID )
191             {
192                 EndCopy();
193                 eErr = CreateFeatureViaInsert( poFeature );
194             }
195             else
196             {
197                 if ( !bCopyActive )
198                 {
199                     // This is a heuristics. If the first feature to be copied
200                     // has a FID set (and that a FID column has been
201                     // identified), then we will try to copy FID values from
202                     // features. Otherwise, we will not do and assume that the
203                     // FID column is an autoincremented column.
204                     StartCopy(bFIDSet);
205                     bCopyStatementWithFID = bFIDSet;
206                     bNeedToUpdateSequence = bFIDSet;
207                 }
208 
209                 eErr = CreateFeatureViaCopy( poFeature );
210                 if( bFIDSet )
211                     bAutoFIDOnCreateViaCopy = false;
212                 if( eErr == OGRERR_NONE && bAutoFIDOnCreateViaCopy )
213                 {
214                     poFeature->SetFID( ++iNextShapeId );
215                 }
216             }
217         }
218     }
219 
220     if( eErr == OGRERR_NONE && iFIDAsRegularColumnIndex >= 0 )
221     {
222         poFeature->SetField(iFIDAsRegularColumnIndex, poFeature->GetFID());
223     }
224     return eErr;
225 }
226 
227 /************************************************************************/
228 /*                       CreateFeatureViaInsert()                       */
229 /************************************************************************/
230 
CreateFeatureViaInsert(OGRFeature * poFeature)231 OGRErr OGRPGDumpLayer::CreateFeatureViaInsert( OGRFeature *poFeature )
232 
233 {
234     OGRErr eErr = OGRERR_FAILURE;
235 
236     if( nullptr == poFeature )
237     {
238         CPLError( CE_Failure, CPLE_AppDefined,
239                   "NULL pointer to OGRFeature passed to CreateFeatureViaInsert()." );
240         return eErr;
241     }
242 
243 /* -------------------------------------------------------------------- */
244 /*      Form the INSERT command.                                        */
245 /* -------------------------------------------------------------------- */
246     CPLString osCommand;
247     osCommand.Printf( "INSERT INTO %s (", pszSqlTableName );
248 
249     bool bNeedComma = false;
250 
251     for( int i = 0; i < poFeatureDefn->GetGeomFieldCount(); i++ )
252     {
253         OGRGeometry *poGeom = poFeature->GetGeomFieldRef(i);
254         if( poGeom != nullptr )
255         {
256             if( bNeedComma )
257                 osCommand += ", ";
258 
259             OGRGeomFieldDefn* poGFldDefn = poFeature->GetGeomFieldDefnRef(i);
260             osCommand = osCommand + OGRPGDumpEscapeColumnName(poGFldDefn->GetNameRef()) + " ";
261             bNeedComma = true;
262         }
263     }
264 
265     if( poFeature->GetFID() != OGRNullFID && pszFIDColumn != nullptr )
266     {
267         bNeedToUpdateSequence = true;
268         if( bNeedComma )
269             osCommand += ", ";
270 
271         osCommand = osCommand + OGRPGDumpEscapeColumnName(pszFIDColumn) + " ";
272         bNeedComma = true;
273     }
274     else
275     {
276         UpdateSequenceIfNeeded();
277     }
278 
279     for( int i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
280     {
281         if( i == iFIDAsRegularColumnIndex )
282             continue;
283         if( !poFeature->IsFieldSet( i ) )
284             continue;
285 
286         if( !bNeedComma )
287             bNeedComma = true;
288         else
289             osCommand += ", ";
290 
291         osCommand = osCommand
292             + OGRPGDumpEscapeColumnName(poFeatureDefn->GetFieldDefn(i)->GetNameRef());
293     }
294 
295     const bool bEmptyInsert = !bNeedComma;
296 
297     osCommand += ") VALUES (";
298 
299     /* Set the geometry */
300     bNeedComma = false;
301     for( int i = 0; i < poFeatureDefn->GetGeomFieldCount(); i++ )
302     {
303         OGRGeometry *poGeom = poFeature->GetGeomFieldRef(i);
304         if( poGeom != nullptr )
305         {
306             char *pszWKT = nullptr;
307 
308             OGRPGDumpGeomFieldDefn* poGFldDefn =
309                 (OGRPGDumpGeomFieldDefn*) poFeature->GetGeomFieldDefnRef(i);
310 
311             poGeom->closeRings();
312             poGeom->set3D(poGFldDefn->GeometryTypeFlags & OGRGeometry::OGR_G_3D);
313             poGeom->setMeasured(poGFldDefn->GeometryTypeFlags & OGRGeometry::OGR_G_MEASURED);
314 
315             if( bNeedComma )
316                 osCommand += ", ";
317 
318             if( bWriteAsHex )
319             {
320                 char* pszHex = OGRGeometryToHexEWKB( poGeom, poGFldDefn->nSRSId,
321                                                      nPostGISMajor,
322                                                      nPostGISMinor );
323                 osCommand += "'";
324                 if (pszHex)
325                     osCommand += pszHex;
326                 osCommand += "'";
327                 CPLFree(pszHex);
328             }
329             else
330             {
331                 poGeom->exportToWkt( &pszWKT, wkbVariantIso );
332 
333                 if( pszWKT != nullptr )
334                 {
335                     osCommand +=
336                         CPLString().Printf(
337                             "GeomFromEWKT('SRID=%d;%s'::TEXT) ", poGFldDefn->nSRSId, pszWKT );
338                     CPLFree( pszWKT );
339                 }
340                 else
341                     osCommand += "''";
342             }
343 
344             bNeedComma = true;
345         }
346     }
347 
348     /* Set the FID */
349     if( poFeature->GetFID() != OGRNullFID && pszFIDColumn != nullptr )
350     {
351         if( bNeedComma )
352             osCommand += ", ";
353         osCommand += CPLString().Printf( CPL_FRMT_GIB, poFeature->GetFID() );
354         bNeedComma = true;
355     }
356 
357     for( int i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
358     {
359         if( i == iFIDAsRegularColumnIndex )
360             continue;
361         if( !poFeature->IsFieldSet( i ) )
362             continue;
363 
364         if( bNeedComma )
365             osCommand += ", ";
366         else
367             bNeedComma = true;
368 
369         OGRPGCommonAppendFieldValue(osCommand, poFeature, i,
370                                     OGRPGDumpEscapeStringWithUserData, nullptr);
371     }
372 
373     osCommand += ")";
374 
375     if( bEmptyInsert )
376         osCommand.Printf( "INSERT INTO %s DEFAULT VALUES", pszSqlTableName );
377 
378 /* -------------------------------------------------------------------- */
379 /*      Execute the insert.                                             */
380 /* -------------------------------------------------------------------- */
381     poDS->Log(osCommand);
382 
383     if( poFeature->GetFID() == OGRNullFID )
384         poFeature->SetFID( ++iNextShapeId );
385 
386     return OGRERR_NONE;
387 }
388 
389 /************************************************************************/
390 /*                        CreateFeatureViaCopy()                        */
391 /************************************************************************/
392 
CreateFeatureViaCopy(OGRFeature * poFeature)393 OGRErr OGRPGDumpLayer::CreateFeatureViaCopy( OGRFeature *poFeature )
394 {
395     CPLString            osCommand;
396 
397     /* First process geometry */
398     for( int i = 0; i < poFeature->GetGeomFieldCount(); i++ )
399     {
400         OGRGeometry *poGeometry = poFeature->GetGeomFieldRef(i);
401         char *pszGeom = nullptr;
402         if ( nullptr != poGeometry /* && (bHasWkb || bHasPostGISGeometry || bHasPostGISGeography) */)
403         {
404             OGRPGDumpGeomFieldDefn* poGFldDefn =
405                 (OGRPGDumpGeomFieldDefn*) poFeature->GetGeomFieldDefnRef(i);
406 
407             poGeometry->closeRings();
408             poGeometry->set3D(poGFldDefn->GeometryTypeFlags & OGRGeometry::OGR_G_3D);
409             poGeometry->setMeasured(poGFldDefn->GeometryTypeFlags & OGRGeometry::OGR_G_MEASURED);
410 
411             //CheckGeomTypeCompatibility(poGeometry);
412 
413             /*if (bHasWkb)
414                 pszGeom = GeometryToBYTEA( poGeometry );
415             else*/
416                 pszGeom = OGRGeometryToHexEWKB( poGeometry, poGFldDefn->nSRSId,
417                                                 nPostGISMajor,
418                                                 nPostGISMinor );
419         }
420 
421         if (!osCommand.empty())
422             osCommand += "\t";
423         if ( pszGeom )
424         {
425             osCommand += pszGeom;
426             CPLFree( pszGeom );
427         }
428         else
429         {
430             osCommand += "\\N";
431         }
432     }
433 
434     OGRPGCommonAppendCopyFieldsExceptGeom(osCommand,
435                                           poFeature,
436                                           pszFIDColumn,
437                                           bFIDColumnInCopyFields,
438                                           std::vector<bool>(poFeatureDefn->GetFieldCount(), true),
439                                           OGRPGDumpEscapeStringWithUserData,
440                                           nullptr);
441 
442     /* Add end of line marker */
443     // osCommand += "\n";
444 
445     /* ------------------------------------------------------------ */
446     /*      Execute the copy.                                       */
447     /* ------------------------------------------------------------ */
448 
449     OGRErr result = OGRERR_NONE;
450 
451     poDS->Log(osCommand, false);
452 
453     return result;
454 }
455 
456 /************************************************************************/
457 /*                OGRPGCommonAppendCopyFieldsExceptGeom()               */
458 /************************************************************************/
459 
OGRPGCommonAppendCopyFieldsExceptGeom(CPLString & osCommand,OGRFeature * poFeature,const char * pszFIDColumn,bool bFIDColumnInCopyFields,const std::vector<bool> & abFieldsToInclude,OGRPGCommonEscapeStringCbk pfnEscapeString,void * userdata)460 void OGRPGCommonAppendCopyFieldsExceptGeom(
461     CPLString& osCommand,
462     OGRFeature* poFeature,
463     const char* pszFIDColumn,
464     bool bFIDColumnInCopyFields,
465     const std::vector<bool>& abFieldsToInclude,
466     OGRPGCommonEscapeStringCbk pfnEscapeString,
467     void* userdata )
468 {
469     OGRFeatureDefn* poFeatureDefn = poFeature->GetDefnRef();
470 
471     /* Next process the field id column */
472     int nFIDIndex = -1;
473     if( bFIDColumnInCopyFields )
474     {
475         if (!osCommand.empty())
476             osCommand += "\t";
477 
478         nFIDIndex = poFeatureDefn->GetFieldIndex( pszFIDColumn );
479 
480         /* Set the FID */
481         if( poFeature->GetFID() != OGRNullFID )
482         {
483             osCommand += CPLString().Printf( CPL_FRMT_GIB, poFeature->GetFID());
484         }
485         else
486         {
487             osCommand += "\\N" ;
488         }
489     }
490 
491     /* Now process the remaining fields */
492 
493     int nFieldCount = poFeatureDefn->GetFieldCount();
494     bool bAddTab = !osCommand.empty();
495 
496     CPLAssert( nFieldCount == static_cast<int>(abFieldsToInclude.size()) );
497 
498     for( int i = 0; i < nFieldCount;  i++ )
499     {
500         if (i == nFIDIndex)
501             continue;
502         if( !abFieldsToInclude[i] )
503             continue;
504 
505         const char *pszStrValue = poFeature->GetFieldAsString(i);
506         char *pszNeedToFree = nullptr;
507 
508         if( bAddTab )
509             osCommand += "\t";
510         bAddTab = true;
511 
512         if( !poFeature->IsFieldSetAndNotNull( i ) )
513         {
514             osCommand += "\\N" ;
515 
516             continue;
517         }
518 
519         const int nOGRFieldType = poFeatureDefn->GetFieldDefn(i)->GetType();
520 
521         // We need special formatting for integer list values.
522         if( nOGRFieldType == OFTIntegerList )
523         {
524             int nCount, nOff = 0;
525             const int *panItems = poFeature->GetFieldAsIntegerList(i,&nCount);
526 
527             const size_t nLen = nCount * 13 + 10;
528             pszNeedToFree = (char *) CPLMalloc(nLen);
529             strcpy( pszNeedToFree, "{" );
530             for( int j = 0; j < nCount; j++ )
531             {
532                 if( j != 0 )
533                     strcat( pszNeedToFree+nOff, "," );
534 
535                 nOff += static_cast<int>(strlen(pszNeedToFree+nOff));
536                 snprintf( pszNeedToFree+nOff, nLen-nOff, "%d", panItems[j] );
537             }
538             strcat( pszNeedToFree+nOff, "}" );
539             pszStrValue = pszNeedToFree;
540         }
541 
542         else if( nOGRFieldType == OFTInteger64List )
543         {
544             int nCount, nOff = 0;
545             const GIntBig *panItems = poFeature->GetFieldAsInteger64List(i,&nCount);
546 
547             const size_t nLen = nCount * 26 + 10;
548             pszNeedToFree = (char *) CPLMalloc(nLen);
549             strcpy( pszNeedToFree, "{" );
550             for( int j = 0; j < nCount; j++ )
551             {
552                 if( j != 0 )
553                     strcat( pszNeedToFree+nOff, "," );
554 
555                 nOff += static_cast<int>(strlen(pszNeedToFree+nOff));
556                 snprintf( pszNeedToFree+nOff, nLen-nOff, CPL_FRMT_GIB, panItems[j] );
557             }
558             strcat( pszNeedToFree+nOff, "}" );
559             pszStrValue = pszNeedToFree;
560         }
561 
562         // We need special formatting for real list values.
563         else if( nOGRFieldType == OFTRealList )
564         {
565             int nOff = 0;
566             int nCount = 0;
567             const double *padfItems =
568                 poFeature->GetFieldAsDoubleList(i,&nCount);
569 
570             const size_t nLen = nCount * 40 + 10;
571             pszNeedToFree = (char *) CPLMalloc(nLen);
572             strcpy( pszNeedToFree, "{" );
573             for( int j = 0; j < nCount; j++ )
574             {
575                 if( j != 0 )
576                     strcat( pszNeedToFree+nOff, "," );
577 
578                 nOff += static_cast<int>(strlen(pszNeedToFree+nOff));
579                 //Check for special values. They need to be quoted.
580                 if( CPLIsNan(padfItems[j]) )
581                     snprintf( pszNeedToFree+nOff, nLen-nOff, "NaN" );
582                 else if( CPLIsInf(padfItems[j]) )
583                     snprintf( pszNeedToFree+nOff, nLen-nOff, (padfItems[j] > 0) ? "Infinity" : "-Infinity" );
584                 else
585                     CPLsnprintf( pszNeedToFree+nOff, nLen-nOff, "%.16g", padfItems[j] );
586             }
587             strcat( pszNeedToFree+nOff, "}" );
588             pszStrValue = pszNeedToFree;
589         }
590 
591         // We need special formatting for string list values.
592         else if( nOGRFieldType == OFTStringList )
593         {
594             CPLString osStr;
595             char **papszItems = poFeature->GetFieldAsStringList(i);
596 
597             pszStrValue = pszNeedToFree = CPLStrdup(
598                 OGRPGDumpEscapeStringList(papszItems, false,
599                                           pfnEscapeString, userdata));
600         }
601 
602         // Binary formatting
603         else if( nOGRFieldType == OFTBinary )
604         {
605             int nLen = 0;
606             GByte* pabyData = poFeature->GetFieldAsBinary( i, &nLen );
607             char* pszBytea = OGRPGDumpLayer::GByteArrayToBYTEA( pabyData, nLen);
608 
609             pszStrValue = pszNeedToFree = pszBytea;
610         }
611 
612         else if( nOGRFieldType == OFTReal )
613         {
614             //Check for special values. They need to be quoted.
615             double dfVal = poFeature->GetFieldAsDouble(i);
616             if( CPLIsNan(dfVal) )
617                 pszStrValue = "NaN";
618             else if( CPLIsInf(dfVal) )
619                 pszStrValue = (dfVal > 0) ? "Infinity" : "-Infinity";
620         }
621 
622         if( nOGRFieldType != OFTIntegerList &&
623             nOGRFieldType != OFTInteger64List &&
624             nOGRFieldType != OFTRealList &&
625             nOGRFieldType != OFTInteger &&
626             nOGRFieldType != OFTInteger64 &&
627             nOGRFieldType != OFTReal &&
628             nOGRFieldType != OFTBinary )
629         {
630             int iUTFChar = 0;
631             const int nMaxWidth = poFeatureDefn->GetFieldDefn(i)->GetWidth();
632 
633             for( int iChar = 0; pszStrValue[iChar] != '\0'; iChar++ )
634             {
635                 //count of utf chars
636                 if (nOGRFieldType != OFTStringList && (pszStrValue[iChar] & 0xc0) != 0x80)
637                 {
638                     if( nMaxWidth > 0 && iUTFChar == nMaxWidth )
639                     {
640                         CPLDebug( "PG",
641                                 "Truncated %s field value, it was too long.",
642                                 poFeatureDefn->GetFieldDefn(i)->GetNameRef() );
643                         break;
644                     }
645                     iUTFChar++;
646                 }
647 
648                 /* Escape embedded \, \t, \n, \r since they will cause COPY
649                    to misinterpret a line of text and thus abort */
650                 if( pszStrValue[iChar] == '\\' ||
651                     pszStrValue[iChar] == '\t' ||
652                     pszStrValue[iChar] == '\r' ||
653                     pszStrValue[iChar] == '\n'   )
654                 {
655                     osCommand += '\\';
656                 }
657 
658                 osCommand += pszStrValue[iChar];
659             }
660         }
661         else
662         {
663             osCommand += pszStrValue;
664         }
665 
666         if( pszNeedToFree )
667             CPLFree( pszNeedToFree );
668     }
669 }
670 
671 /************************************************************************/
672 /*                             StartCopy()                              */
673 /************************************************************************/
674 
StartCopy(int bSetFID)675 OGRErr OGRPGDumpLayer::StartCopy( int bSetFID )
676 
677 {
678     /* Tell the datasource we are now planning to copy data */
679     poDS->StartCopy( this );
680 
681     CPLString osFields = BuildCopyFields(bSetFID);
682 
683     size_t size = osFields.size() +  strlen(pszSqlTableName) + 100;
684     char *pszCommand = (char *) CPLMalloc(size);
685 
686     snprintf( pszCommand, size,
687              "COPY %s (%s) FROM STDIN",
688              pszSqlTableName, osFields.c_str() );
689 
690     poDS->Log(pszCommand);
691     bCopyActive = true;
692 
693     CPLFree( pszCommand );
694 
695     return OGRERR_NONE;
696 }
697 
698 /************************************************************************/
699 /*                              EndCopy()                               */
700 /************************************************************************/
701 
EndCopy()702 OGRErr OGRPGDumpLayer::EndCopy()
703 
704 {
705     if( !bCopyActive )
706         return OGRERR_NONE;
707 
708     bCopyActive = false;
709 
710     poDS->Log("\\.", false);
711     poDS->Log("END");
712 
713     bUseCopy = USE_COPY_UNSET;
714 
715     UpdateSequenceIfNeeded();
716 
717     return OGRERR_NONE;
718 }
719 
720 /************************************************************************/
721 /*                       UpdateSequenceIfNeeded()                       */
722 /************************************************************************/
723 
UpdateSequenceIfNeeded()724 void OGRPGDumpLayer::UpdateSequenceIfNeeded()
725 {
726     if( bNeedToUpdateSequence && pszFIDColumn != nullptr )
727     {
728         CPLString osCommand;
729         osCommand.Printf(
730             "SELECT setval(pg_get_serial_sequence(%s, %s), MAX(%s)) FROM %s",
731             OGRPGDumpEscapeString(pszSqlTableName).c_str(),
732             OGRPGDumpEscapeString(pszFIDColumn).c_str(),
733             OGRPGDumpEscapeColumnName(pszFIDColumn).c_str(),
734             pszSqlTableName);
735         poDS->Log(osCommand);
736         bNeedToUpdateSequence = false;
737     }
738 }
739 
740 /************************************************************************/
741 /*                          BuildCopyFields()                           */
742 /************************************************************************/
743 
BuildCopyFields(int bSetFID)744 CPLString OGRPGDumpLayer::BuildCopyFields( int bSetFID )
745 {
746     CPLString osFieldList;
747 
748     for( int i = 0; i < poFeatureDefn->GetGeomFieldCount(); i++ )
749     {
750         if( !osFieldList.empty() )
751             osFieldList += ", ";
752 
753         OGRGeomFieldDefn* poGFldDefn = poFeatureDefn->GetGeomFieldDefn(i);
754 
755         osFieldList += OGRPGDumpEscapeColumnName(poGFldDefn->GetNameRef());
756     }
757 
758     int nFIDIndex = -1;
759     bFIDColumnInCopyFields = pszFIDColumn != nullptr && bSetFID;
760     if( bFIDColumnInCopyFields )
761     {
762         if( !osFieldList.empty() )
763             osFieldList += ", ";
764 
765         nFIDIndex = poFeatureDefn->GetFieldIndex( pszFIDColumn );
766 
767         osFieldList += OGRPGDumpEscapeColumnName(pszFIDColumn);
768     }
769 
770     for( int i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
771     {
772         if (i == nFIDIndex)
773             continue;
774 
775         const char *pszName = poFeatureDefn->GetFieldDefn(i)->GetNameRef();
776 
777        if( !osFieldList.empty() )
778             osFieldList += ", ";
779 
780         osFieldList += OGRPGDumpEscapeColumnName(pszName);
781     }
782 
783     return osFieldList;
784 }
785 
786 /************************************************************************/
787 /*                       OGRPGDumpEscapeColumnName( )                   */
788 /************************************************************************/
789 
OGRPGDumpEscapeColumnName(const char * pszColumnName)790 CPLString OGRPGDumpEscapeColumnName(const char* pszColumnName)
791 {
792     CPLString osStr = "\"";
793 
794     char ch = '\0';
795     for( int i = 0; (ch = pszColumnName[i]) != '\0'; i++ )
796     {
797         if (ch == '"')
798             osStr.append(1, ch);
799         osStr.append(1, ch);
800     }
801 
802     osStr += "\"";
803 
804     return osStr;
805 }
806 
807 /************************************************************************/
808 /*                             EscapeString( )                          */
809 /************************************************************************/
810 
OGRPGDumpEscapeString(const char * pszStrValue,int nMaxLength,const char * pszFieldName)811 CPLString OGRPGDumpEscapeString( const char* pszStrValue, int nMaxLength,
812                                  const char* pszFieldName )
813 {
814     CPLString osCommand;
815 
816     /* We need to quote and escape string fields. */
817     osCommand += "'";
818 
819     int nSrcLen = static_cast<int>(strlen(pszStrValue));
820     const int nSrcLenUTF = CPLStrlenUTF8(pszStrValue);
821 
822     if (nMaxLength > 0 && nSrcLenUTF > nMaxLength)
823     {
824         CPLDebug( "PG",
825                   "Truncated %s field value, it was too long.",
826                   pszFieldName );
827 
828         int iUTF8Char = 0;
829         for(int iChar = 0; iChar < nSrcLen; iChar++ )
830         {
831             if( (((unsigned char *) pszStrValue)[iChar] & 0xc0) != 0x80 )
832             {
833                 if( iUTF8Char == nMaxLength )
834                 {
835                     nSrcLen = iChar;
836                     break;
837                 }
838                 iUTF8Char ++;
839             }
840         }
841     }
842 
843     char* pszDestStr = (char*)CPLMalloc(2 * nSrcLen + 1);
844 
845     /* -------------------------------------------------------------------- */
846     /*  PQescapeStringConn was introduced in PostgreSQL security releases   */
847     /*  8.1.4, 8.0.8, 7.4.13, 7.3.15                                        */
848     /*  PG_HAS_PQESCAPESTRINGCONN is added by a test in 'configure'         */
849     /*  so it is not set by default when building OGR for Win32             */
850     /* -------------------------------------------------------------------- */
851 #if defined(PG_HAS_PQESCAPESTRINGCONN)
852     int nError = 0;
853     PQescapeStringConn (hPGConn, pszDestStr, pszStrValue, nSrcLen, &nError);
854     if (nError == 0)
855         osCommand += pszDestStr;
856     else
857         CPLError(CE_Warning, CPLE_AppDefined,
858                  "PQescapeString(): %s\n"
859                  "  input: '%s'\n"
860                  "    got: '%s'\n",
861                  PQerrorMessage( hPGConn ),
862                  pszStrValue, pszDestStr );
863 #else
864     //PQescapeString(pszDestStr, pszStrValue, nSrcLen);
865 
866     int j = 0;  // Used after for.
867     for( int i = 0; i < nSrcLen; i++)
868     {
869         if (pszStrValue[i] == '\'')
870         {
871             pszDestStr[j++] = '\'';
872             pszDestStr[j++] = '\'';
873         }
874         // FIXME: at some point (when we drop PostgreSQL < 9.1 support, remove
875         // the escaping of backslash and remove
876         //   'SET standard_conforming_strings = OFF'
877         //  in ICreateLayer().
878         else if (pszStrValue[i] == '\\')
879         {
880             pszDestStr[j++] = '\\';
881             pszDestStr[j++] = '\\';
882         }
883         else
884         {
885             pszDestStr[j++] = pszStrValue[i];
886         }
887     }
888     pszDestStr[j] = 0;
889 
890     osCommand += pszDestStr;
891 #endif
892     CPLFree(pszDestStr);
893 
894     osCommand += "'";
895 
896     return osCommand;
897 }
898 
899 /************************************************************************/
900 /*                    OGRPGDumpEscapeStringList( )                      */
901 /************************************************************************/
902 
OGRPGDumpEscapeStringList(char ** papszItems,bool bForInsertOrUpdate,OGRPGCommonEscapeStringCbk pfnEscapeString,void * userdata)903 static CPLString OGRPGDumpEscapeStringList(
904     char** papszItems, bool bForInsertOrUpdate,
905     OGRPGCommonEscapeStringCbk pfnEscapeString,
906     void* userdata)
907 {
908     bool bFirstItem = true;
909     CPLString osStr;
910     if (bForInsertOrUpdate)
911         osStr += "ARRAY[";
912     else
913         osStr += "{";
914     while(papszItems && *papszItems)
915     {
916         if( !bFirstItem )
917         {
918             osStr += ',';
919         }
920 
921         char* pszStr = *papszItems;
922         if (*pszStr != '\0')
923         {
924             if (bForInsertOrUpdate)
925                 osStr += pfnEscapeString(userdata, pszStr, 0, "", "");
926             else
927             {
928                 osStr += '"';
929 
930                 while(*pszStr)
931                 {
932                     if (*pszStr == '"' )
933                         osStr += "\\";
934                     osStr += *pszStr;
935                     pszStr++;
936                 }
937 
938                 osStr += '"';
939             }
940         }
941         else
942             osStr += "NULL";
943 
944         bFirstItem = false;
945 
946         papszItems++;
947     }
948     if (bForInsertOrUpdate)
949     {
950         osStr += "]";
951         if( papszItems == nullptr )
952             osStr += "::varchar[]";
953     }
954     else
955         osStr += "}";
956     return osStr;
957 }
958 
959 /************************************************************************/
960 /*                          AppendFieldValue()                          */
961 /*                                                                      */
962 /* Used by CreateFeatureViaInsert() and SetFeature() to format a        */
963 /* non-empty field value                                                */
964 /************************************************************************/
965 
OGRPGCommonAppendFieldValue(CPLString & osCommand,OGRFeature * poFeature,int i,OGRPGCommonEscapeStringCbk pfnEscapeString,void * userdata)966 void OGRPGCommonAppendFieldValue(CPLString& osCommand,
967                                  OGRFeature* poFeature, int i,
968                                  OGRPGCommonEscapeStringCbk pfnEscapeString,
969                                  void* userdata)
970 {
971     if( poFeature->IsFieldNull(i) )
972     {
973         osCommand += "NULL";
974         return;
975     }
976 
977     OGRFeatureDefn* poFeatureDefn = poFeature->GetDefnRef();
978     OGRFieldType nOGRFieldType = poFeatureDefn->GetFieldDefn(i)->GetType();
979     OGRFieldSubType eSubType = poFeatureDefn->GetFieldDefn(i)->GetSubType();
980 
981     // We need special formatting for integer list values.
982     if(  nOGRFieldType == OFTIntegerList )
983     {
984         int nCount, nOff = 0, j;
985         const int *panItems = poFeature->GetFieldAsIntegerList(i,&nCount);
986 
987         const size_t nLen = nCount * 13 + 10;
988         char *pszNeedToFree = (char *) CPLMalloc(nLen);
989         strcpy( pszNeedToFree, "'{" );
990         for( j = 0; j < nCount; j++ )
991         {
992             if( j != 0 )
993                 strcat( pszNeedToFree+nOff, "," );
994 
995             nOff += static_cast<int>(strlen(pszNeedToFree+nOff));
996             snprintf( pszNeedToFree+nOff, nLen-nOff, "%d", panItems[j] );
997         }
998         strcat( pszNeedToFree+nOff, "}'" );
999 
1000         osCommand += pszNeedToFree;
1001         CPLFree(pszNeedToFree);
1002 
1003         return;
1004     }
1005 
1006     else if(  nOGRFieldType == OFTInteger64List )
1007     {
1008         int nCount, nOff = 0, j;
1009         const GIntBig *panItems = poFeature->GetFieldAsInteger64List(i,&nCount);
1010 
1011         const size_t nLen = nCount * 26 + 10;
1012         char *pszNeedToFree = (char *) CPLMalloc(nLen);
1013         strcpy( pszNeedToFree, "'{" );
1014         for( j = 0; j < nCount; j++ )
1015         {
1016             if( j != 0 )
1017                 strcat( pszNeedToFree+nOff, "," );
1018 
1019             nOff += static_cast<int>(strlen(pszNeedToFree+nOff));
1020             snprintf( pszNeedToFree+nOff, nLen-nOff, CPL_FRMT_GIB, panItems[j] );
1021         }
1022         strcat( pszNeedToFree+nOff, "}'" );
1023 
1024         osCommand += pszNeedToFree;
1025         CPLFree(pszNeedToFree);
1026 
1027         return;
1028     }
1029 
1030     // We need special formatting for real list values.
1031     else if( nOGRFieldType == OFTRealList )
1032     {
1033         int nCount = 0;
1034         int nOff = 0;
1035         const double *padfItems = poFeature->GetFieldAsDoubleList(i,&nCount);
1036 
1037         const size_t nLen = nCount * 40 + 10;
1038         char *pszNeedToFree = (char *) CPLMalloc(nLen);
1039         strcpy( pszNeedToFree, "'{" );
1040         for( int j = 0; j < nCount; j++ )
1041         {
1042             if( j != 0 )
1043                 strcat( pszNeedToFree+nOff, "," );
1044 
1045             nOff += static_cast<int>(strlen(pszNeedToFree+nOff));
1046             //Check for special values. They need to be quoted.
1047             if( CPLIsNan(padfItems[j]) )
1048                 snprintf( pszNeedToFree+nOff, nLen-nOff, "NaN" );
1049             else if( CPLIsInf(padfItems[j]) )
1050                 snprintf( pszNeedToFree+nOff, nLen-nOff, (padfItems[j] > 0) ? "Infinity" : "-Infinity" );
1051             else
1052                 CPLsnprintf( pszNeedToFree+nOff, nLen-nOff, "%.16g", padfItems[j] );
1053         }
1054         strcat( pszNeedToFree+nOff, "}'" );
1055 
1056         osCommand += pszNeedToFree;
1057         CPLFree(pszNeedToFree);
1058 
1059         return;
1060     }
1061 
1062     // We need special formatting for string list values.
1063     else if( nOGRFieldType == OFTStringList )
1064     {
1065         char **papszItems = poFeature->GetFieldAsStringList(i);
1066 
1067         osCommand += OGRPGDumpEscapeStringList(papszItems, true,
1068                                                pfnEscapeString, userdata);
1069 
1070         return;
1071     }
1072 
1073     // Binary formatting
1074     else if( nOGRFieldType == OFTBinary )
1075     {
1076         osCommand += "E'";
1077 
1078         int nLen = 0;
1079         GByte* pabyData = poFeature->GetFieldAsBinary( i, &nLen );
1080         char* pszBytea = OGRPGDumpLayer::GByteArrayToBYTEA( pabyData, nLen);
1081 
1082         osCommand += pszBytea;
1083 
1084         CPLFree(pszBytea);
1085         osCommand += "'";
1086 
1087         return;
1088     }
1089 
1090     // Flag indicating NULL or not-a-date date value
1091     // e.g. 0000-00-00 - there is no year 0
1092     bool bIsDateNull = false;
1093 
1094     const char *pszStrValue = poFeature->GetFieldAsString(i);
1095 
1096     // Check if date is NULL: 0000-00-00
1097     if( nOGRFieldType == OFTDate )
1098     {
1099         if( STARTS_WITH_CI(pszStrValue, "0000") )
1100         {
1101             pszStrValue = "NULL";
1102             bIsDateNull = true;
1103         }
1104     }
1105     else if ( nOGRFieldType == OFTReal )
1106     {
1107         //Check for special values. They need to be quoted.
1108         double dfVal = poFeature->GetFieldAsDouble(i);
1109         if( CPLIsNan(dfVal) )
1110             pszStrValue = "'NaN'";
1111         else if( CPLIsInf(dfVal) )
1112             pszStrValue = (dfVal > 0) ? "'Infinity'" : "'-Infinity'";
1113     }
1114     else if ( (nOGRFieldType == OFTInteger ||
1115                nOGRFieldType == OFTInteger64) && eSubType == OFSTBoolean )
1116         pszStrValue = poFeature->GetFieldAsInteger(i) ? "'t'" : "'f'";
1117 
1118     if( nOGRFieldType != OFTInteger && nOGRFieldType != OFTInteger64 &&
1119         nOGRFieldType != OFTReal && nOGRFieldType != OFTStringList
1120         && !bIsDateNull )
1121     {
1122         osCommand += pfnEscapeString( userdata, pszStrValue,
1123                                       poFeatureDefn->GetFieldDefn(i)->GetWidth(),
1124                                       poFeatureDefn->GetName(),
1125                                       poFeatureDefn->GetFieldDefn(i)->GetNameRef() );
1126     }
1127     else
1128     {
1129         osCommand += pszStrValue;
1130     }
1131 }
1132 
1133 /************************************************************************/
1134 /*                        GByteArrayToBYTEA()                           */
1135 /************************************************************************/
1136 
GByteArrayToBYTEA(const GByte * pabyData,int nLen)1137 char* OGRPGDumpLayer::GByteArrayToBYTEA( const GByte* pabyData, int nLen)
1138 {
1139     const size_t nTextBufLen = nLen * 5 + 1;
1140     char* pszTextBuf;
1141     pszTextBuf = (char *) CPLMalloc(nTextBufLen);
1142 
1143     int iDst = 0;
1144 
1145     for( int iSrc = 0; iSrc < nLen; iSrc++ )
1146     {
1147         if( pabyData[iSrc] < 40 || pabyData[iSrc] > 126
1148             || pabyData[iSrc] == '\\' )
1149         {
1150             snprintf( pszTextBuf+iDst, nTextBufLen - iDst, "\\\\%03o", pabyData[iSrc] );
1151             iDst += 5;
1152         }
1153         else
1154             pszTextBuf[iDst++] = pabyData[iSrc];
1155     }
1156     pszTextBuf[iDst] = '\0';
1157 
1158     return pszTextBuf;
1159 }
1160 
1161 /************************************************************************/
1162 /*                       OGRPGCommonLayerGetType()                      */
1163 /************************************************************************/
1164 
OGRPGCommonLayerGetType(OGRFieldDefn & oField,bool bPreservePrecision,bool bApproxOK)1165 CPLString OGRPGCommonLayerGetType( OGRFieldDefn& oField,
1166                                    bool bPreservePrecision,
1167                                    bool bApproxOK )
1168 {
1169     const char* pszFieldType = "";
1170 
1171 /* -------------------------------------------------------------------- */
1172 /*      Work out the PostgreSQL type.                                   */
1173 /* -------------------------------------------------------------------- */
1174     if( oField.GetType() == OFTInteger )
1175     {
1176         if( oField.GetSubType() == OFSTBoolean )
1177             pszFieldType = "BOOLEAN";
1178         else if( oField.GetSubType() == OFSTInt16 )
1179             pszFieldType = "SMALLINT";
1180         else if( oField.GetWidth() > 0 && bPreservePrecision )
1181             pszFieldType = CPLSPrintf( "NUMERIC(%d,0)", oField.GetWidth() );
1182         else
1183             pszFieldType = "INTEGER";
1184     }
1185     else if( oField.GetType() == OFTInteger64 )
1186     {
1187         if( oField.GetWidth() > 0 && bPreservePrecision )
1188             pszFieldType = CPLSPrintf( "NUMERIC(%d,0)", oField.GetWidth() );
1189         else
1190             pszFieldType = "INT8";
1191     }
1192     else if( oField.GetType() == OFTReal )
1193     {
1194         if( oField.GetSubType() == OFSTFloat32 )
1195             pszFieldType = "REAL";
1196         else if( oField.GetWidth() > 0 &&
1197                  oField.GetPrecision() > 0 &&
1198                  bPreservePrecision )
1199             pszFieldType = CPLSPrintf( "NUMERIC(%d,%d)",
1200                      oField.GetWidth(), oField.GetPrecision() );
1201         else
1202             pszFieldType = "FLOAT8";
1203     }
1204     else if( oField.GetType() == OFTString )
1205     {
1206         if (oField.GetSubType() == OFSTJSON )
1207             pszFieldType = CPLGetConfigOption("OGR_PG_JSON_TYPE", "JSON");
1208         else if (oField.GetSubType() == OFSTUUID )
1209             pszFieldType = CPLGetConfigOption("OGR_PG_UUID_TYPE", "UUID");
1210         else if (oField.GetWidth() > 0 && oField.GetWidth() < 10485760 && bPreservePrecision )
1211             pszFieldType = CPLSPrintf( "VARCHAR(%d)",  oField.GetWidth() );
1212         else
1213             pszFieldType = CPLGetConfigOption("OGR_PG_STRING_TYPE", "VARCHAR");
1214     }
1215     else if( oField.GetType() == OFTIntegerList )
1216     {
1217         if( oField.GetSubType() == OFSTBoolean )
1218             pszFieldType = "BOOLEAN[]";
1219         else if( oField.GetSubType() == OFSTInt16 )
1220             pszFieldType = "INT2[]";
1221         else
1222             pszFieldType = "INTEGER[]";
1223     }
1224     else if( oField.GetType() == OFTInteger64List )
1225     {
1226         pszFieldType = "INT8[]";
1227     }
1228     else if( oField.GetType() == OFTRealList )
1229     {
1230         if( oField.GetSubType() == OFSTFloat32 )
1231             pszFieldType = "REAL[]";
1232         else
1233             pszFieldType = "FLOAT8[]";
1234     }
1235     else if( oField.GetType() == OFTStringList )
1236     {
1237         pszFieldType = "varchar[]";
1238     }
1239     else if( oField.GetType() == OFTDate )
1240     {
1241         pszFieldType = "date";
1242     }
1243     else if( oField.GetType() == OFTTime )
1244     {
1245         pszFieldType = "time";
1246     }
1247     else if( oField.GetType() == OFTDateTime )
1248     {
1249         pszFieldType = "timestamp with time zone";
1250     }
1251     else if( oField.GetType() == OFTBinary )
1252     {
1253         pszFieldType = "bytea";
1254     }
1255     else if( bApproxOK )
1256     {
1257         CPLError( CE_Warning, CPLE_NotSupported,
1258                   "Can't create field %s with type %s on PostgreSQL layers.  Creating as VARCHAR.",
1259                   oField.GetNameRef(),
1260                   OGRFieldDefn::GetFieldTypeName(oField.GetType()) );
1261         pszFieldType = "VARCHAR";
1262     }
1263     else
1264     {
1265         CPLError( CE_Failure, CPLE_NotSupported,
1266                   "Can't create field %s with type %s on PostgreSQL layers.",
1267                   oField.GetNameRef(),
1268                   OGRFieldDefn::GetFieldTypeName(oField.GetType()) );
1269     }
1270 
1271     return pszFieldType;
1272 }
1273 
1274 /************************************************************************/
1275 /*                         OGRPGCommonLayerSetType()                    */
1276 /************************************************************************/
1277 
OGRPGCommonLayerSetType(OGRFieldDefn & oField,const char * pszType,const char * pszFormatType,int nWidth)1278 bool OGRPGCommonLayerSetType( OGRFieldDefn& oField,
1279                               const char* pszType,
1280                               const char* pszFormatType,
1281                               int nWidth )
1282 {
1283     if( EQUAL(pszType,"text") )
1284     {
1285         oField.SetType( OFTString );
1286     }
1287     else if( EQUAL(pszType,"_bpchar") ||
1288             EQUAL(pszType,"_varchar") ||
1289             EQUAL(pszType,"_text"))
1290     {
1291         oField.SetType( OFTStringList );
1292     }
1293     else if( EQUAL(pszType,"bpchar") || EQUAL(pszType,"varchar") )
1294     {
1295         if( nWidth == -1 )
1296         {
1297             if( STARTS_WITH_CI(pszFormatType, "character(") )
1298                 nWidth = atoi(pszFormatType+10);
1299             else if( STARTS_WITH_CI(pszFormatType, "character varying(") )
1300                 nWidth = atoi(pszFormatType+18);
1301             else
1302                 nWidth = 0;
1303         }
1304         oField.SetType( OFTString );
1305         oField.SetWidth( nWidth );
1306     }
1307     else if( EQUAL(pszType,"bool") )
1308     {
1309         oField.SetType( OFTInteger );
1310         oField.SetSubType( OFSTBoolean );
1311         oField.SetWidth( 1 );
1312     }
1313     else if( EQUAL(pszType,"_numeric") )
1314     {
1315         if( EQUAL(pszFormatType, "numeric[]") )
1316             oField.SetType( OFTRealList );
1317         else
1318         {
1319             const char *pszPrecision = strstr(pszFormatType,",");
1320             int    nPrecision = 0;
1321 
1322             nWidth = atoi(pszFormatType + 8);
1323             if( pszPrecision != nullptr )
1324                 nPrecision = atoi(pszPrecision+1);
1325 
1326             if( nPrecision == 0 )
1327             {
1328                 if( nWidth >= 10 )
1329                     oField.SetType( OFTInteger64List );
1330                 else
1331                     oField.SetType( OFTIntegerList );
1332             }
1333             else
1334                 oField.SetType( OFTRealList );
1335 
1336             oField.SetWidth( nWidth );
1337             oField.SetPrecision( nPrecision );
1338         }
1339     }
1340     else if( EQUAL(pszType,"numeric") )
1341     {
1342         if( EQUAL(pszFormatType, "numeric") )
1343             oField.SetType( OFTReal );
1344         else
1345         {
1346             const char *pszPrecision = strstr(pszFormatType,",");
1347             int    nPrecision = 0;
1348 
1349             nWidth = atoi(pszFormatType + 8);
1350             if( pszPrecision != nullptr )
1351                 nPrecision = atoi(pszPrecision+1);
1352 
1353             if( nPrecision == 0 )
1354             {
1355                 if( nWidth >= 10 )
1356                     oField.SetType( OFTInteger64 );
1357                 else
1358                     oField.SetType( OFTInteger );
1359             }
1360             else
1361                 oField.SetType( OFTReal );
1362 
1363             oField.SetWidth( nWidth );
1364             oField.SetPrecision( nPrecision );
1365         }
1366     }
1367     else if( EQUAL(pszFormatType,"integer[]") )
1368     {
1369         oField.SetType( OFTIntegerList );
1370     }
1371     else if( EQUAL(pszFormatType,"smallint[]") )
1372     {
1373         oField.SetType( OFTIntegerList );
1374         oField.SetSubType( OFSTInt16 );
1375     }
1376     else if( EQUAL(pszFormatType,"boolean[]") )
1377     {
1378         oField.SetType( OFTIntegerList );
1379         oField.SetSubType( OFSTBoolean );
1380     }
1381     else if( EQUAL(pszFormatType, "float[]") ||
1382             EQUAL(pszFormatType, "real[]") )
1383     {
1384         oField.SetType( OFTRealList );
1385         oField.SetSubType( OFSTFloat32 );
1386     }
1387     else if( EQUAL(pszFormatType, "double precision[]") )
1388     {
1389         oField.SetType( OFTRealList );
1390     }
1391     else if( EQUAL(pszType,"int2") )
1392     {
1393         oField.SetType( OFTInteger );
1394         oField.SetSubType( OFSTInt16 );
1395         oField.SetWidth( 5 );
1396     }
1397     else if( EQUAL(pszType,"int8") )
1398     {
1399         oField.SetType( OFTInteger64 );
1400     }
1401     else if( EQUAL(pszFormatType,"bigint[]") )
1402     {
1403         oField.SetType( OFTInteger64List );
1404     }
1405     else if( STARTS_WITH_CI(pszType, "int") )
1406     {
1407         oField.SetType( OFTInteger );
1408     }
1409     else if( EQUAL(pszType,"float4")  )
1410     {
1411         oField.SetType( OFTReal );
1412         oField.SetSubType( OFSTFloat32 );
1413     }
1414     else if( STARTS_WITH_CI(pszType, "float") ||
1415             STARTS_WITH_CI(pszType, "double") ||
1416             EQUAL(pszType,"real") )
1417     {
1418         oField.SetType( OFTReal );
1419     }
1420     else if( STARTS_WITH_CI(pszType, "timestamp") )
1421     {
1422         oField.SetType( OFTDateTime );
1423     }
1424     else if( STARTS_WITH_CI(pszType, "date") )
1425     {
1426         oField.SetType( OFTDate );
1427     }
1428     else if( STARTS_WITH_CI(pszType, "time") )
1429     {
1430         oField.SetType( OFTTime );
1431     }
1432     else if( EQUAL(pszType,"bytea") )
1433     {
1434         oField.SetType( OFTBinary );
1435     }
1436     else if( EQUAL(pszType,"json") || EQUAL(pszType, "jsonb") )
1437     {
1438         oField.SetType( OFTString );
1439         oField.SetSubType( OFSTJSON );
1440     }
1441     else if( EQUAL(pszType,"uuid") )
1442     {
1443         oField.SetType( OFTString );
1444         oField.SetSubType( OFSTUUID );
1445     }
1446     else
1447     {
1448         CPLDebug( "PGCommon", "Field %s is of unknown format type %s (type=%s).",
1449                 oField.GetNameRef(), pszFormatType, pszType );
1450         return false;
1451     }
1452     return true;
1453 }
1454 
1455 /************************************************************************/
1456 /*                  OGRPGCommonLayerNormalizeDefault()                  */
1457 /************************************************************************/
1458 
OGRPGCommonLayerNormalizeDefault(OGRFieldDefn * poFieldDefn,const char * pszDefault)1459 void OGRPGCommonLayerNormalizeDefault(OGRFieldDefn* poFieldDefn,
1460                                       const char* pszDefault)
1461 {
1462     if(pszDefault==nullptr)
1463         return;
1464     CPLString osDefault(pszDefault);
1465     size_t nPos = osDefault.find("::character varying");
1466     if( nPos != std::string::npos &&
1467         nPos + strlen("::character varying") == osDefault.size() )
1468     {
1469         osDefault.resize(nPos);
1470     }
1471     else if( (nPos = osDefault.find("::text")) != std::string::npos &&
1472              nPos + strlen("::text") == osDefault.size() )
1473     {
1474         osDefault.resize(nPos);
1475     }
1476     else if( strcmp(osDefault, "now()") == 0 )
1477         osDefault = "CURRENT_TIMESTAMP";
1478     else if( strcmp(osDefault, "('now'::text)::date") == 0 )
1479         osDefault = "CURRENT_DATE";
1480     else if( strcmp(osDefault, "('now'::text)::time with time zone") == 0 )
1481         osDefault = "CURRENT_TIME";
1482     else
1483     {
1484         nPos = osDefault.find("::timestamp with time zone");
1485         if( poFieldDefn->GetType() == OFTDateTime && nPos != std::string::npos )
1486         {
1487             osDefault.resize(nPos);
1488             nPos = osDefault.find("'+");
1489             if( nPos != std::string::npos )
1490             {
1491                 osDefault.resize(nPos);
1492                 osDefault += "'";
1493             }
1494             int nYear = 0;
1495             int nMonth = 0;
1496             int nDay = 0;
1497             int nHour = 0;
1498             int nMinute = 0;
1499             float fSecond = 0.0f;
1500             if( sscanf(osDefault, "'%d-%d-%d %d:%d:%f'", &nYear, &nMonth, &nDay,
1501                                 &nHour, &nMinute, &fSecond) == 6 ||
1502                 sscanf(osDefault, "'%d-%d-%d %d:%d:%f+00'", &nYear, &nMonth, &nDay,
1503                                 &nHour, &nMinute, &fSecond) == 6)
1504             {
1505                 if( osDefault.find('.') == std::string::npos )
1506                     osDefault = CPLSPrintf("'%04d/%02d/%02d %02d:%02d:%02d'",
1507                                             nYear, nMonth, nDay, nHour, nMinute, (int)(fSecond+0.5));
1508                 else
1509                     osDefault = CPLSPrintf("'%04d/%02d/%02d %02d:%02d:%06.3f'",
1510                                                     nYear, nMonth, nDay, nHour, nMinute, fSecond);
1511             }
1512         }
1513     }
1514     poFieldDefn->SetDefault(osDefault);
1515 }
1516 
1517 /************************************************************************/
1518 /*                     OGRPGCommonLayerGetPGDefault()                   */
1519 /************************************************************************/
1520 
OGRPGCommonLayerGetPGDefault(OGRFieldDefn * poFieldDefn)1521 CPLString OGRPGCommonLayerGetPGDefault(OGRFieldDefn* poFieldDefn)
1522 {
1523     CPLString osRet = poFieldDefn->GetDefault();
1524     int nYear = 0;
1525     int nMonth = 0;
1526     int nDay = 0;
1527     int nHour = 0;
1528     int nMinute = 0;
1529     float fSecond = 0.0f;
1530     if( sscanf(osRet, "'%d/%d/%d %d:%d:%f'",
1531                 &nYear, &nMonth, &nDay,
1532                 &nHour, &nMinute, &fSecond) == 6 )
1533     {
1534         osRet.resize(osRet.size()-1);
1535         osRet += "+00'::timestamp with time zone";
1536     }
1537     return osRet;
1538 }
1539 
1540 /************************************************************************/
1541 /*                           GetNextFeature()                           */
1542 /************************************************************************/
1543 
CreateField(OGRFieldDefn * poFieldIn,int bApproxOK)1544 OGRErr OGRPGDumpLayer::CreateField( OGRFieldDefn *poFieldIn,
1545                                     int bApproxOK )
1546 {
1547     CPLString osFieldType;
1548     OGRFieldDefn oField( poFieldIn );
1549 
1550     // Can be set to NO to test ogr2ogr default behavior
1551     const bool bAllowCreationOfFieldWithFIDName =
1552         CPLTestBool(CPLGetConfigOption(
1553             "PGDUMP_DEBUG_ALLOW_CREATION_FIELD_WITH_FID_NAME", "YES"));
1554 
1555     if( bAllowCreationOfFieldWithFIDName && pszFIDColumn != nullptr &&
1556         EQUAL( oField.GetNameRef(), pszFIDColumn ) &&
1557         oField.GetType() != OFTInteger &&
1558         oField.GetType() != OFTInteger64 )
1559     {
1560         CPLError(CE_Failure, CPLE_AppDefined, "Wrong field type for %s",
1561                  oField.GetNameRef());
1562         return OGRERR_FAILURE;
1563     }
1564 
1565 /* -------------------------------------------------------------------- */
1566 /*      Do we want to "launder" the column names into Postgres          */
1567 /*      friendly format?                                                */
1568 /* -------------------------------------------------------------------- */
1569     if( bLaunderColumnNames )
1570     {
1571         char *pszSafeName =
1572             OGRPGCommonLaunderName( oField.GetNameRef(), "PGDump" );
1573 
1574         oField.SetName( pszSafeName );
1575         CPLFree( pszSafeName );
1576 
1577         if( EQUAL(oField.GetNameRef(),"oid") )
1578         {
1579             CPLError( CE_Warning, CPLE_AppDefined,
1580                       "Renaming field 'oid' to 'oid_' to avoid conflict with "
1581                       "internal oid field." );
1582             oField.SetName( "oid_" );
1583         }
1584     }
1585 
1586     const char* pszOverrideType =
1587         CSLFetchNameValue(papszOverrideColumnTypes, oField.GetNameRef());
1588     if( pszOverrideType != nullptr )
1589     {
1590         osFieldType = pszOverrideType;
1591     }
1592     else
1593     {
1594         osFieldType =
1595             OGRPGCommonLayerGetType(oField, bPreservePrecision,
1596                                     CPL_TO_BOOL(bApproxOK));
1597         if (osFieldType.empty())
1598             return OGRERR_FAILURE;
1599     }
1600 
1601 /* -------------------------------------------------------------------- */
1602 /*      Create the new field.                                           */
1603 /* -------------------------------------------------------------------- */
1604     CPLString osCommand;
1605     osCommand.Printf( "ALTER TABLE %s ADD COLUMN %s %s",
1606                       pszSqlTableName,
1607                       OGRPGDumpEscapeColumnName(oField.GetNameRef()).c_str(),
1608                       osFieldType.c_str() );
1609     if( !oField.IsNullable() )
1610         osCommand += " NOT NULL";
1611     if( oField.IsUnique() )
1612         osCommand += " UNIQUE";
1613     if( oField.GetDefault() != nullptr && !oField.IsDefaultDriverSpecific() )
1614     {
1615         osCommand += " DEFAULT ";
1616         osCommand += OGRPGCommonLayerGetPGDefault(&oField);
1617     }
1618 
1619     poFeatureDefn->AddFieldDefn( &oField );
1620 
1621     if( bAllowCreationOfFieldWithFIDName && pszFIDColumn != nullptr &&
1622         EQUAL( oField.GetNameRef(), pszFIDColumn ) )
1623     {
1624         iFIDAsRegularColumnIndex = poFeatureDefn->GetFieldCount() - 1;
1625     }
1626     else
1627     {
1628         if( bCreateTable )
1629             poDS->Log(osCommand);
1630     }
1631 
1632     return OGRERR_NONE;
1633 }
1634 
1635 /************************************************************************/
1636 /*                           CreateGeomField()                          */
1637 /************************************************************************/
1638 
CreateGeomField(OGRGeomFieldDefn * poGeomFieldIn,int)1639 OGRErr OGRPGDumpLayer::CreateGeomField( OGRGeomFieldDefn *poGeomFieldIn,
1640                                         int /* bApproxOK */ )
1641 {
1642     OGRwkbGeometryType eType = poGeomFieldIn->GetType();
1643     if( eType == wkbNone )
1644     {
1645         CPLError(CE_Failure, CPLE_AppDefined,
1646                  "Cannot create geometry field of type wkbNone");
1647         return OGRERR_FAILURE;
1648     }
1649 
1650     // Check if GEOMETRY_NAME layer creation option was set, but no initial
1651     // column was created in ICreateLayer()
1652     const CPLString osGeomFieldName =
1653         !m_osFirstGeometryFieldName.empty()
1654         ? m_osFirstGeometryFieldName
1655         : CPLString(poGeomFieldIn->GetNameRef());
1656 
1657     m_osFirstGeometryFieldName = ""; // reset for potential next geom columns
1658 
1659     OGRGeomFieldDefn oTmpGeomFieldDefn( poGeomFieldIn );
1660     oTmpGeomFieldDefn.SetName(osGeomFieldName);
1661 
1662     CPLString               osCommand;
1663     OGRPGDumpGeomFieldDefn *poGeomField =
1664         new OGRPGDumpGeomFieldDefn( &oTmpGeomFieldDefn );
1665 
1666 /* -------------------------------------------------------------------- */
1667 /*      Do we want to "launder" the column names into Postgres          */
1668 /*      friendly format?                                                */
1669 /* -------------------------------------------------------------------- */
1670     if( bLaunderColumnNames )
1671     {
1672         char *pszSafeName =
1673             OGRPGCommonLaunderName( poGeomField->GetNameRef(), "PGDump" );
1674 
1675         poGeomField->SetName( pszSafeName );
1676         CPLFree( pszSafeName );
1677     }
1678 
1679     OGRSpatialReference* poSRS = poGeomField->GetSpatialRef();
1680     int nSRSId = nUnknownSRSId;
1681     if( nForcedSRSId != -2 )
1682         nSRSId = nForcedSRSId;
1683     else if( poSRS != nullptr )
1684     {
1685         const char* pszAuthorityName = poSRS->GetAuthorityName(nullptr);
1686         if( pszAuthorityName != nullptr && EQUAL( pszAuthorityName, "EPSG" ) )
1687         {
1688             /* Assume the EPSG Id is the SRS ID. Might be a wrong guess ! */
1689             nSRSId = atoi( poSRS->GetAuthorityCode(nullptr) );
1690         }
1691         else
1692         {
1693             const char* pszGeogCSName = poSRS->GetAttrValue("GEOGCS");
1694             if (pszGeogCSName != nullptr && EQUAL(pszGeogCSName, "GCS_WGS_1984"))
1695                 nSRSId = 4326;
1696         }
1697     }
1698 
1699     poGeomField->nSRSId = nSRSId;
1700 
1701     int GeometryTypeFlags = 0;
1702     if( OGR_GT_HasZ((OGRwkbGeometryType)eType) )
1703         GeometryTypeFlags |= OGRGeometry::OGR_G_3D;
1704     if( OGR_GT_HasM((OGRwkbGeometryType)eType) )
1705         GeometryTypeFlags |= OGRGeometry::OGR_G_MEASURED;
1706     if( nForcedGeometryTypeFlags >= 0 )
1707     {
1708         GeometryTypeFlags = nForcedGeometryTypeFlags;
1709         eType = OGR_GT_SetModifier(eType,
1710                                    GeometryTypeFlags & OGRGeometry::OGR_G_3D,
1711                                    GeometryTypeFlags & OGRGeometry::OGR_G_MEASURED);
1712     }
1713     poGeomField->SetType(eType);
1714     poGeomField->GeometryTypeFlags = GeometryTypeFlags;
1715 
1716 /* -------------------------------------------------------------------- */
1717 /*      Create the new field.                                           */
1718 /* -------------------------------------------------------------------- */
1719     if (bCreateTable)
1720     {
1721         const char *suffix = "";
1722         int dim = 2;
1723         if( (poGeomField->GeometryTypeFlags & OGRGeometry::OGR_G_3D) && (poGeomField->GeometryTypeFlags & OGRGeometry::OGR_G_MEASURED) )
1724             dim = 4;
1725         else if( poGeomField->GeometryTypeFlags & OGRGeometry::OGR_G_MEASURED )
1726         {
1727             if( wkbFlatten(poGeomField->GetType()) != wkbUnknown )
1728                 suffix = "M";
1729             dim = 3;
1730         }
1731         else if( poGeomField->GeometryTypeFlags & OGRGeometry::OGR_G_3D )
1732             dim = 3;
1733 
1734         const char *pszGeometryType = OGRToOGCGeomType(poGeomField->GetType());
1735         osCommand.Printf(
1736                 "SELECT AddGeometryColumn(%s,%s,%s,%d,'%s%s',%d)",
1737                 OGRPGDumpEscapeString(pszSchemaName).c_str(),
1738                 OGRPGDumpEscapeString(poFeatureDefn->GetName()).c_str(),
1739                 OGRPGDumpEscapeString(poGeomField->GetNameRef()).c_str(),
1740                 nSRSId, pszGeometryType, suffix, dim );
1741 
1742         poDS->Log(osCommand);
1743 
1744         if( !poGeomField->IsNullable() )
1745         {
1746             osCommand.Printf( "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL",
1747                               OGRPGDumpEscapeColumnName(poFeatureDefn->GetName()).c_str(),
1748                               OGRPGDumpEscapeColumnName(poGeomField->GetNameRef()).c_str() );
1749 
1750             poDS->Log(osCommand);
1751         }
1752 
1753         if( bCreateSpatialIndexFlag )
1754         {
1755             osCommand.Printf("CREATE INDEX %s ON %s USING %s (%s)",
1756                             OGRPGDumpEscapeColumnName(
1757                                 CPLSPrintf("%s_%s_geom_idx", GetName(), poGeomField->GetNameRef())).c_str(),
1758                             pszSqlTableName,
1759                             osSpatialIndexType.c_str(),
1760                             OGRPGDumpEscapeColumnName(poGeomField->GetNameRef()).c_str());
1761 
1762             poDS->Log(osCommand);
1763         }
1764     }
1765 
1766     poFeatureDefn->AddGeomFieldDefn( poGeomField, FALSE );
1767 
1768     return OGRERR_NONE;
1769 }
1770 
1771 /************************************************************************/
1772 /*                        SetOverrideColumnTypes()                      */
1773 /************************************************************************/
1774 
SetOverrideColumnTypes(const char * pszOverrideColumnTypes)1775 void OGRPGDumpLayer::SetOverrideColumnTypes( const char* pszOverrideColumnTypes )
1776 {
1777     if( pszOverrideColumnTypes == nullptr )
1778         return;
1779 
1780     const char* pszIter = pszOverrideColumnTypes;
1781     CPLString osCur;
1782     while(*pszIter != '\0')
1783     {
1784         if( *pszIter == '(' )
1785         {
1786             /* Ignore commas inside ( ) pair */
1787             while(*pszIter != '\0')
1788             {
1789                 if( *pszIter == ')' )
1790                 {
1791                     osCur += *pszIter;
1792                     pszIter ++;
1793                     break;
1794                 }
1795                 osCur += *pszIter;
1796                 pszIter ++;
1797             }
1798             if( *pszIter == '\0')
1799                 break;
1800         }
1801 
1802         if( *pszIter == ',' )
1803         {
1804             papszOverrideColumnTypes = CSLAddString(papszOverrideColumnTypes, osCur);
1805             osCur = "";
1806         }
1807         else
1808             osCur += *pszIter;
1809         pszIter ++;
1810     }
1811     if( !osCur.empty() )
1812         papszOverrideColumnTypes = CSLAddString(papszOverrideColumnTypes, osCur);
1813 }
1814 
1815 /************************************************************************/
1816 /*                              SetMetadata()                           */
1817 /************************************************************************/
1818 
SetMetadata(char ** papszMD,const char * pszDomain)1819 CPLErr OGRPGDumpLayer::SetMetadata(char** papszMD, const char* pszDomain)
1820 {
1821     OGRLayer::SetMetadata(papszMD, pszDomain);
1822     if( !osForcedDescription.empty() &&
1823         (pszDomain == nullptr || EQUAL(pszDomain, "")) )
1824     {
1825         OGRLayer::SetMetadataItem("DESCRIPTION", osForcedDescription);
1826     }
1827 
1828     if( (pszDomain == nullptr || EQUAL(pszDomain, "")) &&
1829         osForcedDescription.empty() )
1830     {
1831         const char* l_pszDescription = OGRLayer::GetMetadataItem("DESCRIPTION");
1832         CPLString osCommand;
1833 
1834         osCommand.Printf( "COMMENT ON TABLE %s IS %s",
1835                            pszSqlTableName,
1836                            l_pszDescription && l_pszDescription[0] != '\0' ?
1837                               OGRPGDumpEscapeString(l_pszDescription).c_str() : "NULL" );
1838         poDS->Log( osCommand );
1839     }
1840 
1841     return CE_None;
1842 }
1843 
1844 /************************************************************************/
1845 /*                            SetMetadataItem()                         */
1846 /************************************************************************/
1847 
SetMetadataItem(const char * pszName,const char * pszValue,const char * pszDomain)1848 CPLErr OGRPGDumpLayer::SetMetadataItem(const char* pszName, const char* pszValue,
1849                                        const char* pszDomain)
1850 {
1851     if( (pszDomain == nullptr || EQUAL(pszDomain, "")) && pszName != nullptr &&
1852         EQUAL(pszName, "DESCRIPTION") && !osForcedDescription.empty() )
1853     {
1854         return CE_None;
1855     }
1856     OGRLayer::SetMetadataItem(pszName, pszValue, pszDomain);
1857     if( (pszDomain == nullptr || EQUAL(pszDomain, "")) && pszName != nullptr &&
1858         EQUAL(pszName, "DESCRIPTION") )
1859     {
1860         SetMetadata( GetMetadata() );
1861     }
1862     return CE_None;
1863 }
1864 
1865 /************************************************************************/
1866 /*                      SetForcedDescription()                          */
1867 /************************************************************************/
1868 
SetForcedDescription(const char * pszDescriptionIn)1869 void OGRPGDumpLayer::SetForcedDescription( const char* pszDescriptionIn )
1870 {
1871     osForcedDescription = pszDescriptionIn;
1872     OGRLayer::SetMetadataItem("DESCRIPTION", osForcedDescription);
1873 
1874     if( pszDescriptionIn[0] != '\0' )
1875     {
1876         CPLString osCommand;
1877         osCommand.Printf( "COMMENT ON TABLE %s IS %s",
1878                             pszSqlTableName,
1879                             OGRPGDumpEscapeString(pszDescriptionIn).c_str() );
1880         poDS->Log( osCommand );
1881     }
1882 }
1883