LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/mssqlspatial - ogrmssqlspatialtablelayer.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 0 819 0.0 %
Date: 2024-11-21 22:18:42 Functions: 0 28 0.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  MSSQL Spatial driver
       4             :  * Purpose:  Implements OGRMSSQLSpatialTableLayer class, access to an existing
       5             :  *table. Author:   Tamas Szekeres, szekerest at gmail.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2010, Tamas Szekeres
       9             :  * Copyright (c) 2010-2012, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "cpl_conv.h"
      15             : #include "ogr_mssqlspatial.h"
      16             : #include "ogr_p.h"
      17             : 
      18             : #include <cmath>
      19             : #include <memory>
      20             : #include <string_view>
      21             : 
      22             : #define UNSUPPORTED_OP_READ_ONLY                                               \
      23             :     "%s : unsupported operation on a read-only datasource."
      24             : 
      25             : /************************************************************************/
      26             : /*                         OGRMSSQLAppendEscaped( )                     */
      27             : /************************************************************************/
      28             : 
      29           0 : void OGRMSSQLAppendEscaped(CPLODBCStatement *poStatement,
      30             :                            const char *pszStrValue)
      31             : {
      32           0 :     if (!pszStrValue)
      33             :     {
      34           0 :         poStatement->Append("null");
      35           0 :         return;
      36             :     }
      37             : 
      38           0 :     size_t iIn, iOut, nTextLen = strlen(pszStrValue);
      39           0 :     char *pszEscapedText = (char *)CPLMalloc(nTextLen * 2 + 3);
      40             : 
      41           0 :     pszEscapedText[0] = '\'';
      42             : 
      43           0 :     for (iIn = 0, iOut = 1; iIn < nTextLen; iIn++)
      44             :     {
      45           0 :         switch (pszStrValue[iIn])
      46             :         {
      47           0 :             case '\'':
      48           0 :                 pszEscapedText[iOut++] = '\'';  // double quote
      49           0 :                 pszEscapedText[iOut++] = pszStrValue[iIn];
      50           0 :                 break;
      51             : 
      52           0 :             default:
      53           0 :                 pszEscapedText[iOut++] = pszStrValue[iIn];
      54           0 :                 break;
      55             :         }
      56             :     }
      57             : 
      58           0 :     pszEscapedText[iOut++] = '\'';
      59             : 
      60           0 :     pszEscapedText[iOut] = '\0';
      61             : 
      62           0 :     poStatement->Append(pszEscapedText);
      63             : 
      64           0 :     CPLFree(pszEscapedText);
      65             : }
      66             : 
      67             : /************************************************************************/
      68             : /*                          OGRMSSQLSpatialTableLayer()                 */
      69             : /************************************************************************/
      70             : 
      71           0 : OGRMSSQLSpatialTableLayer::OGRMSSQLSpatialTableLayer(
      72           0 :     OGRMSSQLSpatialDataSource *poDSIn)
      73           0 :     : OGRMSSQLSpatialLayer(poDSIn)
      74             : {
      75           0 :     bUseGeometryValidation = CPLTestBool(
      76             :         CPLGetConfigOption("MSSQLSPATIAL_USE_GEOMETRY_VALIDATION", "YES"));
      77           0 : }
      78             : 
      79             : /************************************************************************/
      80             : /*                          ~OGRMSSQLSpatialTableLayer()                */
      81             : /************************************************************************/
      82             : 
      83           0 : OGRMSSQLSpatialTableLayer::~OGRMSSQLSpatialTableLayer()
      84             : 
      85             : {
      86             : #ifdef MSSQL_BCP_SUPPORTED
      87             :     CloseBCP();
      88             : #endif
      89             : 
      90           0 :     if (bNeedSpatialIndex && nLayerStatus == MSSQLLAYERSTATUS_CREATED)
      91             :     {
      92             :         /* recreate spatial index */
      93           0 :         DropSpatialIndex();
      94           0 :         CreateSpatialIndex();
      95             :     }
      96             : 
      97           0 :     CPLFree(pszTableName);
      98           0 :     CPLFree(pszLayerName);
      99           0 :     CPLFree(pszSchemaName);
     100             : 
     101           0 :     CPLFree(pszQuery);
     102           0 :     ClearStatement();
     103           0 : }
     104             : 
     105             : /************************************************************************/
     106             : /*                               GetName()                              */
     107             : /************************************************************************/
     108             : 
     109           0 : const char *OGRMSSQLSpatialTableLayer::GetName()
     110             : 
     111             : {
     112           0 :     return pszLayerName;
     113             : }
     114             : 
     115             : /************************************************************************/
     116             : /*                             GetLayerDefn()                           */
     117             : /************************************************************************/
     118           0 : OGRFeatureDefn *OGRMSSQLSpatialTableLayer::GetLayerDefn()
     119             : {
     120           0 :     if (poFeatureDefn && !bLayerDefnNeedsRefresh)
     121           0 :         return poFeatureDefn;
     122             : 
     123           0 :     CPLODBCSession *poSession = poDS->GetSession();
     124             :     /* -------------------------------------------------------------------- */
     125             :     /*      Do we have a simple primary key?                                */
     126             :     /* -------------------------------------------------------------------- */
     127           0 :     CPLODBCStatement oGetKey(poSession);
     128             : 
     129           0 :     if (oGetKey.GetPrimaryKeys(pszTableName, poDS->GetCatalog(),
     130           0 :                                pszSchemaName) &&
     131           0 :         oGetKey.Fetch())
     132             :     {
     133           0 :         CPLFree(pszFIDColumn);
     134           0 :         pszFIDColumn = CPLStrdup(oGetKey.GetColData(3));
     135             : 
     136           0 :         if (oGetKey.Fetch())  // more than one field in key!
     137             :         {
     138           0 :             oGetKey.Clear();
     139           0 :             CPLFree(pszFIDColumn);
     140           0 :             pszFIDColumn = nullptr;
     141             : 
     142           0 :             CPLDebug("OGR_MSSQLSpatial",
     143             :                      "Table %s has multiple primary key fields, "
     144             :                      "ignoring them all.",
     145             :                      pszTableName);
     146             :         }
     147             :     }
     148             : 
     149             :     /* -------------------------------------------------------------------- */
     150             :     /*      Get the column definitions for this table.                      */
     151             :     /* -------------------------------------------------------------------- */
     152           0 :     CPLODBCStatement oGetCol(poSession);
     153             : 
     154           0 :     if (!oGetCol.GetColumns(pszTableName, poDS->GetCatalog(), pszSchemaName))
     155             :     {
     156           0 :         poFeatureDefn = new OGRFeatureDefn();
     157           0 :         poFeatureDefn->Reference();
     158           0 :         return poFeatureDefn;
     159             :     }
     160             : 
     161           0 :     BuildFeatureDefn(pszLayerName, &oGetCol);
     162             : 
     163           0 :     if (eGeomType != wkbNone)
     164           0 :         poFeatureDefn->SetGeomType(eGeomType);
     165             : 
     166           0 :     if (GetSpatialRef() && poFeatureDefn->GetGeomFieldCount() == 1)
     167           0 :         poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
     168             : 
     169           0 :     if (poFeatureDefn->GetFieldCount() == 0 && pszFIDColumn == nullptr &&
     170           0 :         pszGeomColumn == nullptr)
     171             :     {
     172           0 :         CPLError(
     173             :             CE_Failure, CPLE_AppDefined,
     174             :             "No column definitions found for table '%s', layer not usable.",
     175             :             pszLayerName);
     176           0 :         return poFeatureDefn;
     177             :     }
     178             : 
     179             :     /* -------------------------------------------------------------------- */
     180             :     /*      If we got a geometry column, does it exist?  Is it binary?      */
     181             :     /* -------------------------------------------------------------------- */
     182           0 :     if (pszGeomColumn != nullptr)
     183             :     {
     184           0 :         int iColumn = oGetCol.GetColId(pszGeomColumn);
     185           0 :         if (iColumn < 0)
     186             :         {
     187           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     188             :                      "Column %s requested for geometry, but it does not exist.",
     189             :                      pszGeomColumn);
     190           0 :             CPLFree(pszGeomColumn);
     191           0 :             pszGeomColumn = nullptr;
     192             :         }
     193             :         else
     194             :         {
     195           0 :             if (nGeomColumnType < 0)
     196             :             {
     197             :                 /* last attempt to identify the geometry column type */
     198           0 :                 if (EQUAL(oGetCol.GetColTypeName(iColumn), "geometry"))
     199           0 :                     nGeomColumnType = MSSQLCOLTYPE_GEOMETRY;
     200           0 :                 else if (EQUAL(oGetCol.GetColTypeName(iColumn), "geography"))
     201           0 :                     nGeomColumnType = MSSQLCOLTYPE_GEOGRAPHY;
     202           0 :                 else if (EQUAL(oGetCol.GetColTypeName(iColumn), "varchar"))
     203           0 :                     nGeomColumnType = MSSQLCOLTYPE_TEXT;
     204           0 :                 else if (EQUAL(oGetCol.GetColTypeName(iColumn), "nvarchar"))
     205           0 :                     nGeomColumnType = MSSQLCOLTYPE_TEXT;
     206           0 :                 else if (EQUAL(oGetCol.GetColTypeName(iColumn), "text"))
     207           0 :                     nGeomColumnType = MSSQLCOLTYPE_TEXT;
     208           0 :                 else if (EQUAL(oGetCol.GetColTypeName(iColumn), "ntext"))
     209           0 :                     nGeomColumnType = MSSQLCOLTYPE_TEXT;
     210           0 :                 else if (EQUAL(oGetCol.GetColTypeName(iColumn), "image"))
     211           0 :                     nGeomColumnType = MSSQLCOLTYPE_BINARY;
     212             :                 else
     213             :                 {
     214           0 :                     CPLError(
     215             :                         CE_Failure, CPLE_AppDefined,
     216             :                         "Column type %s is not supported for geometry column.",
     217             :                         oGetCol.GetColTypeName(iColumn));
     218           0 :                     CPLFree(pszGeomColumn);
     219           0 :                     pszGeomColumn = nullptr;
     220             :                 }
     221             :             }
     222             :         }
     223             :     }
     224             : 
     225           0 :     return poFeatureDefn;
     226             : }
     227             : 
     228             : /************************************************************************/
     229             : /*                             Initialize()                             */
     230             : /************************************************************************/
     231             : 
     232           0 : CPLErr OGRMSSQLSpatialTableLayer::Initialize(const char *pszSchema,
     233             :                                              const char *pszLayerNameIn,
     234             :                                              const char *pszGeomCol,
     235             :                                              CPL_UNUSED int nCoordDimension,
     236             :                                              int nSRId, const char *pszSRText,
     237             :                                              OGRwkbGeometryType eType)
     238             : {
     239           0 :     CPLFree(pszFIDColumn);
     240           0 :     pszFIDColumn = nullptr;
     241             : 
     242             :     /* -------------------------------------------------------------------- */
     243             :     /*      Parse out schema name if present in layer.  We assume a         */
     244             :     /*      schema is provided if there is a dot in the name, and that      */
     245             :     /*      it is in the form <schema>.<tablename>                          */
     246             :     /* -------------------------------------------------------------------- */
     247           0 :     const char *pszDot = strstr(pszLayerNameIn, ".");
     248           0 :     if (pszDot != nullptr)
     249             :     {
     250           0 :         pszTableName = CPLStrdup(pszDot + 1);
     251           0 :         if (pszSchema == nullptr)
     252             :         {
     253           0 :             pszSchemaName = CPLStrdup(pszLayerNameIn);
     254           0 :             pszSchemaName[pszDot - pszLayerNameIn] = '\0';
     255             :         }
     256             :         else
     257           0 :             pszSchemaName = CPLStrdup(pszSchema);
     258             : 
     259           0 :         this->pszLayerName = CPLStrdup(pszLayerNameIn);
     260             :     }
     261             :     else
     262             :     {
     263           0 :         pszTableName = CPLStrdup(pszLayerNameIn);
     264           0 :         if (pszSchema == nullptr || EQUAL(pszSchema, "dbo"))
     265             :         {
     266           0 :             pszSchemaName = CPLStrdup("dbo");
     267           0 :             this->pszLayerName = CPLStrdup(pszLayerNameIn);
     268             :         }
     269             :         else
     270             :         {
     271           0 :             pszSchemaName = CPLStrdup(pszSchema);
     272           0 :             this->pszLayerName =
     273           0 :                 CPLStrdup(CPLSPrintf("%s.%s", pszSchemaName, pszTableName));
     274             :         }
     275             :     }
     276           0 :     SetDescription(this->pszLayerName);
     277             : 
     278             :     /* -------------------------------------------------------------------- */
     279             :     /*      Have we been provided a geometry column?                        */
     280             :     /* -------------------------------------------------------------------- */
     281           0 :     CPLFree(pszGeomColumn);
     282           0 :     if (pszGeomCol == nullptr)
     283           0 :         GetLayerDefn(); /* fetch geom column if not specified */
     284             :     else
     285           0 :         pszGeomColumn = CPLStrdup(pszGeomCol);
     286             : 
     287           0 :     if (eType != wkbNone)
     288           0 :         eGeomType = eType;
     289             : 
     290             :     /* -------------------------------------------------------------------- */
     291             :     /*             Try to find out the spatial reference                    */
     292             :     /* -------------------------------------------------------------------- */
     293             : 
     294           0 :     nSRSId = nSRId;
     295             : 
     296           0 :     if (pszSRText)
     297             :     {
     298             :         /* Process srtext directly if specified */
     299           0 :         poSRS = new OGRSpatialReference();
     300           0 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     301           0 :         if (poSRS->importFromWkt(pszSRText) != OGRERR_NONE)
     302             :         {
     303           0 :             delete poSRS;
     304           0 :             poSRS = nullptr;
     305             :         }
     306             :         else
     307             :         {
     308           0 :             const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     309           0 :             const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
     310           0 :             if (pszAuthorityName && pszAuthorityCode &&
     311           0 :                 EQUAL(pszAuthorityName, "EPSG"))
     312             :             {
     313           0 :                 const int nCode = atoi(pszAuthorityCode);
     314           0 :                 poSRS->Clear();
     315           0 :                 poSRS->importFromEPSG(nCode);
     316             :             }
     317             :         }
     318             :     }
     319             : 
     320           0 :     if (!poSRS)
     321             :     {
     322           0 :         if (nSRSId <= 0)
     323           0 :             nSRSId = FetchSRSId();
     324             : 
     325           0 :         GetSpatialRef();
     326             :     }
     327             : 
     328           0 :     if (nSRSId < 0)
     329           0 :         nSRSId = 0;
     330             : 
     331           0 :     return CE_None;
     332             : }
     333             : 
     334             : /************************************************************************/
     335             : /*                         FetchSRSId()                                 */
     336             : /************************************************************************/
     337             : 
     338           0 : int OGRMSSQLSpatialTableLayer::FetchSRSId()
     339             : {
     340           0 :     if (poDS->UseGeometryColumns())
     341             :     {
     342           0 :         CPLODBCStatement oStatement(poDS->GetSession());
     343           0 :         oStatement.Appendf(
     344             :             "select srid from geometry_columns "
     345             :             "where f_table_schema = '%s' and f_table_name = '%s'",
     346             :             pszSchemaName, pszTableName);
     347             : 
     348           0 :         if (oStatement.ExecuteSQL() && oStatement.Fetch())
     349             :         {
     350           0 :             if (oStatement.GetColData(0))
     351           0 :                 nSRSId = atoi(oStatement.GetColData(0));
     352           0 :             if (nSRSId < 0)
     353           0 :                 nSRSId = 0;
     354             :         }
     355             :     }
     356             : 
     357           0 :     return nSRSId;
     358             : }
     359             : 
     360             : /************************************************************************/
     361             : /*                       CreateSpatialIndex()                           */
     362             : /*                                                                      */
     363             : /*      Create a spatial index on the geometry column of the layer      */
     364             : /************************************************************************/
     365             : 
     366           0 : OGRErr OGRMSSQLSpatialTableLayer::CreateSpatialIndex()
     367             : {
     368           0 :     OGRMSSQLSpatialTableLayer::GetLayerDefn();
     369             : 
     370           0 :     if (pszGeomColumn == nullptr)
     371             :     {
     372           0 :         CPLError(CE_Warning, CPLE_AppDefined, "No geometry column found.");
     373           0 :         return OGRERR_FAILURE;
     374             :     }
     375             : 
     376           0 :     CPLODBCStatement oStatement(poDS->GetSession());
     377             : 
     378           0 :     if (nGeomColumnType == MSSQLCOLTYPE_GEOMETRY)
     379             :     {
     380           0 :         OGREnvelope oExt;
     381           0 :         if (GetExtent(&oExt, TRUE) != OGRERR_NONE)
     382             :         {
     383           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     384             :                      "Failed to get extent for spatial index.");
     385           0 :             return OGRERR_FAILURE;
     386             :         }
     387             : 
     388           0 :         if (oExt.MinX == oExt.MaxX || oExt.MinY == oExt.MaxY)
     389           0 :             return OGRERR_NONE; /* skip creating index */
     390             : 
     391           0 :         oStatement.Appendf(
     392             :             "CREATE SPATIAL INDEX [ogr_%s_%s_%s_sidx] ON [%s].[%s] ( [%s] ) "
     393             :             "USING GEOMETRY_GRID WITH (BOUNDING_BOX =(%.15g, %.15g, %.15g, "
     394             :             "%.15g))",
     395             :             pszSchemaName, pszTableName, pszGeomColumn, pszSchemaName,
     396             :             pszTableName, pszGeomColumn, oExt.MinX, oExt.MinY, oExt.MaxX,
     397             :             oExt.MaxY);
     398             :     }
     399           0 :     else if (nGeomColumnType == MSSQLCOLTYPE_GEOGRAPHY)
     400             :     {
     401           0 :         oStatement.Appendf(
     402             :             "CREATE SPATIAL INDEX [ogr_%s_%s_%s_sidx] ON [%s].[%s] ( [%s] ) "
     403             :             "USING GEOGRAPHY_GRID",
     404             :             pszSchemaName, pszTableName, pszGeomColumn, pszSchemaName,
     405             :             pszTableName, pszGeomColumn);
     406             :     }
     407             :     else
     408             :     {
     409           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     410             :                  "Spatial index is not supported on the geometry column '%s'",
     411             :                  pszGeomColumn);
     412           0 :         return OGRERR_FAILURE;
     413             :     }
     414             : 
     415           0 :     if (!oStatement.ExecuteSQL())
     416             :     {
     417           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     418             :                  "Failed to create the spatial index, %s.",
     419           0 :                  poDS->GetSession()->GetLastError());
     420           0 :         return OGRERR_FAILURE;
     421             :     }
     422             : 
     423           0 :     return OGRERR_NONE;
     424             : }
     425             : 
     426             : /************************************************************************/
     427             : /*                       DropSpatialIndex()                             */
     428             : /*                                                                      */
     429             : /*      Drop the spatial index on the geometry column of the layer      */
     430             : /************************************************************************/
     431             : 
     432           0 : void OGRMSSQLSpatialTableLayer::DropSpatialIndex()
     433             : {
     434           0 :     OGRMSSQLSpatialTableLayer::GetLayerDefn();
     435             : 
     436           0 :     CPLODBCStatement oStatement(poDS->GetSession());
     437             : 
     438           0 :     oStatement.Appendf("IF  EXISTS (SELECT * FROM sys.indexes "
     439             :                        "WHERE object_id = OBJECT_ID(N'[%s].[%s]') AND name = "
     440             :                        "N'ogr_%s_%s_%s_sidx') "
     441             :                        "DROP INDEX [ogr_%s_%s_%s_sidx] ON [%s].[%s]",
     442             :                        pszSchemaName, pszTableName, pszSchemaName, pszTableName,
     443             :                        pszGeomColumn, pszSchemaName, pszTableName,
     444             :                        pszGeomColumn, pszSchemaName, pszTableName);
     445             : 
     446           0 :     if (!oStatement.ExecuteSQL())
     447             :     {
     448           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     449             :                  "Failed to drop the spatial index, %s.",
     450           0 :                  poDS->GetSession()->GetLastError());
     451           0 :         return;
     452             :     }
     453             : }
     454             : 
     455             : /************************************************************************/
     456             : /*                     GetBracketEscapedIdentifier()                    */
     457             : /************************************************************************/
     458             : 
     459           0 : static std::string GetBracketEscapedIdentifier(const std::string_view &osStr)
     460             : {
     461           0 :     std::string osRet("[");
     462           0 :     osRet.reserve(osStr.size());
     463           0 :     for (char ch : osStr)
     464             :     {
     465           0 :         if (ch == ']')
     466             :         {
     467           0 :             osRet += ch;
     468             :         }
     469           0 :         osRet += ch;
     470             :     }
     471           0 :     osRet += ']';
     472           0 :     return osRet;
     473             : }
     474             : 
     475             : /************************************************************************/
     476             : /*                            BuildFields()                             */
     477             : /*                                                                      */
     478             : /*      Build list of fields to fetch, performing any required          */
     479             : /*      transformations (such as on geometry).                          */
     480             : /************************************************************************/
     481             : 
     482           0 : CPLString OGRMSSQLSpatialTableLayer::BuildFields()
     483             : 
     484             : {
     485           0 :     int nColumn = 0;
     486           0 :     CPLString osFieldList;
     487             : 
     488           0 :     GetLayerDefn();
     489             : 
     490           0 :     if (pszFIDColumn && poFeatureDefn->GetFieldIndex(pszFIDColumn) == -1)
     491             :     {
     492             :         /* Always get the FID column */
     493           0 :         osFieldList += GetBracketEscapedIdentifier(pszFIDColumn);
     494           0 :         ++nColumn;
     495             :     }
     496             : 
     497           0 :     if (pszGeomColumn && !poFeatureDefn->IsGeometryIgnored())
     498             :     {
     499           0 :         if (nColumn > 0)
     500           0 :             osFieldList += ", ";
     501             : 
     502           0 :         osFieldList += GetBracketEscapedIdentifier(pszGeomColumn);
     503           0 :         if (nGeomColumnType == MSSQLCOLTYPE_GEOMETRY ||
     504           0 :             nGeomColumnType == MSSQLCOLTYPE_GEOGRAPHY)
     505             :         {
     506           0 :             if (poDS->GetGeometryFormat() == MSSQLGEOMETRY_WKB)
     507             :             {
     508           0 :                 osFieldList += ".STAsBinary() as ";
     509           0 :                 osFieldList += GetBracketEscapedIdentifier(pszGeomColumn);
     510             :             }
     511           0 :             else if (poDS->GetGeometryFormat() == MSSQLGEOMETRY_WKT)
     512             :             {
     513           0 :                 osFieldList += ".AsTextZM() as ";
     514           0 :                 osFieldList += GetBracketEscapedIdentifier(pszGeomColumn);
     515             :             }
     516           0 :             else if (poDS->GetGeometryFormat() == MSSQLGEOMETRY_WKBZM)
     517             :             {
     518             :                 /* SQL Server 2012 */
     519           0 :                 osFieldList += ".AsBinaryZM() as ";
     520           0 :                 osFieldList += GetBracketEscapedIdentifier(pszGeomColumn);
     521             :             }
     522             :         }
     523             : 
     524           0 :         ++nColumn;
     525             :     }
     526             : 
     527           0 :     if (poFeatureDefn->GetFieldCount() > 0)
     528             :     {
     529             :         /* need to reconstruct the field ordinals list */
     530           0 :         CPLFree(panFieldOrdinals);
     531           0 :         panFieldOrdinals =
     532           0 :             (int *)CPLMalloc(sizeof(int) * poFeatureDefn->GetFieldCount());
     533             : 
     534           0 :         for (int i = 0; i < poFeatureDefn->GetFieldCount(); i++)
     535             :         {
     536           0 :             if (poFeatureDefn->GetFieldDefn(i)->IsIgnored())
     537           0 :                 continue;
     538             : 
     539           0 :             const char *pszName = poFeatureDefn->GetFieldDefn(i)->GetNameRef();
     540             : 
     541           0 :             if (nColumn > 0)
     542           0 :                 osFieldList += ", ";
     543             : 
     544           0 :             osFieldList += GetBracketEscapedIdentifier(pszName);
     545             : 
     546           0 :             panFieldOrdinals[i] = nColumn;
     547             : 
     548           0 :             ++nColumn;
     549             :         }
     550             :     }
     551             : 
     552           0 :     return osFieldList;
     553             : }
     554             : 
     555             : /************************************************************************/
     556             : /*                            GetStatement()                            */
     557             : /************************************************************************/
     558             : 
     559           0 : CPLODBCStatement *OGRMSSQLSpatialTableLayer::GetStatement()
     560             : 
     561             : {
     562           0 :     if (poStmt == nullptr)
     563             :     {
     564           0 :         poStmt = BuildStatement(BuildFields());
     565             :     }
     566             : 
     567           0 :     return poStmt;
     568             : }
     569             : 
     570             : /************************************************************************/
     571             : /*                           BuildStatement()                           */
     572             : /************************************************************************/
     573             : 
     574             : CPLODBCStatement *
     575           0 : OGRMSSQLSpatialTableLayer::BuildStatement(const char *pszColumns)
     576             : 
     577             : {
     578           0 :     CPLODBCStatement *poStatement = new CPLODBCStatement(poDS->GetSession());
     579           0 :     poStatement->Append("select ");
     580           0 :     poStatement->Append(pszColumns);
     581           0 :     poStatement->Append(" from ");
     582           0 :     poStatement->Append(GetBracketEscapedIdentifier(pszSchemaName));
     583           0 :     poStatement->Append(".");
     584           0 :     poStatement->Append(GetBracketEscapedIdentifier(pszTableName));
     585             : 
     586             :     /* Append attribute query if we have it */
     587           0 :     if (pszQuery != nullptr)
     588           0 :         poStatement->Appendf(" where (%s)", pszQuery);
     589             : 
     590             :     /* If we have a spatial filter, query on it */
     591           0 :     if (m_poFilterGeom != nullptr)
     592             :     {
     593           0 :         if (nGeomColumnType == MSSQLCOLTYPE_GEOMETRY ||
     594           0 :             nGeomColumnType == MSSQLCOLTYPE_GEOGRAPHY)
     595             :         {
     596           0 :             if (!std::isinf(m_sFilterEnvelope.MinX) &&
     597           0 :                 !std::isinf(m_sFilterEnvelope.MinY) &&
     598           0 :                 !std::isinf(m_sFilterEnvelope.MaxX) &&
     599           0 :                 !std::isinf(m_sFilterEnvelope.MaxY))
     600             :             {
     601           0 :                 if (pszQuery == nullptr)
     602           0 :                     poStatement->Append(" where ");
     603             :                 else
     604           0 :                     poStatement->Append(" and ");
     605             : 
     606           0 :                 poStatement->Append(GetBracketEscapedIdentifier(pszGeomColumn));
     607           0 :                 poStatement->Append(".STIntersects(");
     608             : 
     609           0 :                 if (nGeomColumnType == MSSQLCOLTYPE_GEOGRAPHY)
     610           0 :                     poStatement->Append("geography::");
     611             :                 else
     612           0 :                     poStatement->Append("geometry::");
     613             : 
     614           0 :                 if (m_sFilterEnvelope.MinX == m_sFilterEnvelope.MaxX ||
     615           0 :                     m_sFilterEnvelope.MinY == m_sFilterEnvelope.MaxY)
     616           0 :                     poStatement->Appendf(
     617             :                         "STGeomFromText('POINT(%.15g %.15g)',%d)) = 1",
     618             :                         m_sFilterEnvelope.MinX, m_sFilterEnvelope.MinY, nSRSId);
     619             :                 else
     620           0 :                     poStatement->Appendf(
     621             :                         "STGeomFromText('POLYGON((%.15g %.15g,%.15g "
     622             :                         "%.15g,%.15g %.15g,%.15g %.15g,%.15g %.15g))',%d)) = 1",
     623             :                         m_sFilterEnvelope.MinX, m_sFilterEnvelope.MinY,
     624             :                         m_sFilterEnvelope.MaxX, m_sFilterEnvelope.MinY,
     625             :                         m_sFilterEnvelope.MaxX, m_sFilterEnvelope.MaxY,
     626             :                         m_sFilterEnvelope.MinX, m_sFilterEnvelope.MaxY,
     627             :                         m_sFilterEnvelope.MinX, m_sFilterEnvelope.MinY, nSRSId);
     628             :             }
     629             :         }
     630             :         else
     631             :         {
     632           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     633             :                      "Spatial filter is supported only on geometry and "
     634             :                      "geography column types.");
     635             : 
     636           0 :             delete poStatement;
     637           0 :             return nullptr;
     638             :         }
     639             :     }
     640             : 
     641           0 :     CPLDebug("OGR_MSSQLSpatial", "ExecuteSQL(%s)", poStatement->GetCommand());
     642           0 :     if (poStatement->ExecuteSQL())
     643           0 :         return poStatement;
     644             :     else
     645             :     {
     646           0 :         delete poStatement;
     647           0 :         return nullptr;
     648             :     }
     649             : }
     650             : 
     651             : /************************************************************************/
     652             : /*                             GetFeature()                             */
     653             : /************************************************************************/
     654             : 
     655           0 : OGRFeature *OGRMSSQLSpatialTableLayer::GetFeature(GIntBig nFeatureId)
     656             : 
     657             : {
     658           0 :     if (pszFIDColumn == nullptr)
     659           0 :         return OGRMSSQLSpatialLayer::GetFeature(nFeatureId);
     660             : 
     661           0 :     poDS->EndCopy();
     662             : 
     663           0 :     ClearStatement();
     664             : 
     665           0 :     iNextShapeId = nFeatureId;
     666             : 
     667           0 :     m_bResetNeeded = true;
     668           0 :     poStmt = new CPLODBCStatement(poDS->GetSession());
     669           0 :     CPLString osFields = BuildFields();
     670           0 :     poStmt->Appendf("select %s from %s where %s = " CPL_FRMT_GIB,
     671           0 :                     osFields.c_str(), poFeatureDefn->GetName(), pszFIDColumn,
     672             :                     nFeatureId);
     673             : 
     674           0 :     if (!poStmt->ExecuteSQL())
     675             :     {
     676           0 :         delete poStmt;
     677           0 :         poStmt = nullptr;
     678           0 :         return nullptr;
     679             :     }
     680             : 
     681           0 :     return GetNextRawFeature();
     682             : }
     683             : 
     684             : /************************************************************************/
     685             : /*                             GetExtent()                              */
     686             : /*                                                                      */
     687             : /*      For Geometry or Geography types we can use an optimized         */
     688             : /*      statement in other cases we use standard OGRLayer::GetExtent()  */
     689             : /************************************************************************/
     690             : 
     691           0 : OGRErr OGRMSSQLSpatialTableLayer::GetExtent(int iGeomField,
     692             :                                             OGREnvelope *psExtent, int bForce)
     693             : {
     694           0 :     GetLayerDefn();
     695             : 
     696             :     // Make sure we have a geometry field:
     697           0 :     if (iGeomField < 0 || iGeomField >= poFeatureDefn->GetGeomFieldCount() ||
     698           0 :         poFeatureDefn->GetGeomFieldDefn(iGeomField)->GetType() == wkbNone)
     699             :     {
     700           0 :         if (iGeomField != 0)
     701             :         {
     702           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     703             :                      "Invalid geometry field index : %d", iGeomField);
     704             :         }
     705           0 :         return OGRERR_FAILURE;
     706             :     }
     707             : 
     708             :     // If we have a geometry or geography type:
     709           0 :     if (nGeomColumnType == MSSQLCOLTYPE_GEOGRAPHY ||
     710           0 :         nGeomColumnType == MSSQLCOLTYPE_GEOMETRY)
     711             :     {
     712             :         // Prepare statement
     713             :         auto poStatement =
     714           0 :             std::make_unique<CPLODBCStatement>(poDS->GetSession());
     715             : 
     716           0 :         if (poDS->sMSSQLVersion.nMajor >= 11)
     717             :         {
     718             :             // SQLServer 2012 or later:
     719             :             // geography is converted to geometry to obtain the rectangular
     720             :             // envelope
     721           0 :             if (nGeomColumnType == MSSQLCOLTYPE_GEOGRAPHY)
     722           0 :                 poStatement->Appendf(
     723             :                     "WITH extent(extentcol) AS (SELECT "
     724             :                     "geometry::EnvelopeAggregate(geometry::STGeomFromWKB(%s."
     725             :                     "STAsBinary(), %s.STSrid).MakeValid()) as extentcol FROM "
     726             :                     "[%s].[%s])",
     727             :                     pszGeomColumn, pszGeomColumn, pszSchemaName, pszTableName);
     728             :             else
     729           0 :                 poStatement->Appendf("WITH extent(extentcol) AS (SELECT "
     730             :                                      "geometry::EnvelopeAggregate(%s.MakeValid("
     731             :                                      ")) AS extentcol FROM [%s].[%s])",
     732             :                                      pszGeomColumn, pszSchemaName,
     733             :                                      pszTableName);
     734             : 
     735           0 :             poStatement->Appendf(
     736             :                 "SELECT extentcol.STPointN(1).STX, extentcol.STPointN(1).STY,");
     737           0 :             poStatement->Appendf("extentcol.STPointN(3).STX, "
     738             :                                  "extentcol.STPointN(3).STY FROM extent;");
     739             :         }
     740             :         else
     741             :         {
     742             :             // Before 2012 use two CTE's:
     743             :             // geography is converted to geometry to obtain the envelope
     744           0 :             if (nGeomColumnType == MSSQLCOLTYPE_GEOGRAPHY)
     745           0 :                 poStatement->Appendf("WITH ENVELOPE as (SELECT "
     746             :                                      "geometry::STGeomFromWKB(%s.STAsBinary(), "
     747             :                                      "%s.STSrid).MakeValid().STEnvelope() as "
     748             :                                      "envelope from [%s].[%s]),",
     749             :                                      pszGeomColumn, pszGeomColumn,
     750             :                                      pszSchemaName, pszTableName);
     751             :             else
     752           0 :                 poStatement->Appendf(
     753             :                     "WITH ENVELOPE as (SELECT %s.MakeValid().STEnvelope() as "
     754             :                     "envelope from [%s].[%s]),",
     755             :                     pszGeomColumn, pszSchemaName, pszTableName);
     756             : 
     757           0 :             poStatement->Appendf(" CORNERS as (SELECT envelope.STPointN(1) as "
     758             :                                  "point from ENVELOPE UNION ALL select "
     759             :                                  "envelope.STPointN(3) from ENVELOPE)");
     760           0 :             poStatement->Appendf(
     761             :                 "SELECT MIN(point.STX), MIN(point.STY), MAX(point.STX), "
     762             :                 "MAX(point.STY) FROM CORNERS;");
     763             :         }
     764             : 
     765             :         // Execute
     766           0 :         if (!poStatement->ExecuteSQL())
     767             :         {
     768           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Error getting extents, %s",
     769           0 :                      poDS->GetSession()->GetLastError());
     770             :         }
     771             :         else
     772             :         {
     773             :             // Try to update
     774           0 :             while (poStatement->Fetch())
     775             :             {
     776             : 
     777           0 :                 const char *minx = poStatement->GetColData(0);
     778           0 :                 const char *miny = poStatement->GetColData(1);
     779           0 :                 const char *maxx = poStatement->GetColData(2);
     780           0 :                 const char *maxy = poStatement->GetColData(3);
     781             : 
     782           0 :                 if (!(minx == nullptr || miny == nullptr || maxx == nullptr ||
     783             :                       maxy == nullptr))
     784             :                 {
     785           0 :                     psExtent->MinX = CPLAtof(minx);
     786           0 :                     psExtent->MinY = CPLAtof(miny);
     787           0 :                     psExtent->MaxX = CPLAtof(maxx);
     788           0 :                     psExtent->MaxY = CPLAtof(maxy);
     789           0 :                     return OGRERR_NONE;
     790             :                 }
     791             :                 else
     792             :                 {
     793           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     794             :                              "MSSQL extents query returned a NULL value");
     795             :                 }
     796             :             }
     797             :         }
     798             :     }
     799             : 
     800             :     // Fall back to generic implementation (loading all features)
     801           0 :     if (iGeomField == 0)
     802           0 :         return OGRLayer::GetExtent(psExtent, bForce);
     803             :     else
     804           0 :         return OGRLayer::GetExtent(iGeomField, psExtent, bForce);
     805             : }
     806             : 
     807             : /************************************************************************/
     808             : /*                         SetAttributeFilter()                         */
     809             : /************************************************************************/
     810             : 
     811           0 : OGRErr OGRMSSQLSpatialTableLayer::SetAttributeFilter(const char *pszQueryIn)
     812             : 
     813             : {
     814           0 :     CPLFree(m_pszAttrQueryString);
     815           0 :     m_pszAttrQueryString = (pszQueryIn) ? CPLStrdup(pszQueryIn) : nullptr;
     816             : 
     817           0 :     if ((pszQueryIn == nullptr && this->pszQuery == nullptr) ||
     818           0 :         (pszQueryIn != nullptr && this->pszQuery != nullptr &&
     819           0 :          EQUAL(pszQueryIn, this->pszQuery)))
     820           0 :         return OGRERR_NONE;
     821             : 
     822           0 :     CPLFree(this->pszQuery);
     823           0 :     this->pszQuery = (pszQueryIn) ? CPLStrdup(pszQueryIn) : nullptr;
     824             : 
     825           0 :     ClearStatement();
     826             : 
     827           0 :     return OGRERR_NONE;
     828             : }
     829             : 
     830             : /************************************************************************/
     831             : /*                           GetNextFeature()                           */
     832             : /************************************************************************/
     833             : 
     834           0 : OGRFeature *OGRMSSQLSpatialTableLayer::GetNextFeature()
     835             : {
     836           0 :     poDS->EndCopy();
     837           0 :     return OGRMSSQLSpatialLayer::GetNextFeature();
     838             : }
     839             : 
     840             : /************************************************************************/
     841             : /*                           TestCapability()                           */
     842             : /************************************************************************/
     843             : 
     844           0 : int OGRMSSQLSpatialTableLayer::TestCapability(const char *pszCap)
     845             : 
     846             : {
     847           0 :     if (bUpdateAccess)
     848             :     {
     849           0 :         if (EQUAL(pszCap, OLCSequentialWrite) ||
     850           0 :             EQUAL(pszCap, OLCCreateField) || EQUAL(pszCap, OLCDeleteFeature))
     851           0 :             return TRUE;
     852             : 
     853           0 :         else if (EQUAL(pszCap, OLCRandomWrite))
     854           0 :             return pszFIDColumn != nullptr;
     855             :     }
     856             : 
     857             : #if (ODBCVER >= 0x0300)
     858           0 :     if (EQUAL(pszCap, OLCTransactions))
     859           0 :         return TRUE;
     860             : #else
     861             :     if (EQUAL(pszCap, OLCTransactions))
     862             :         return FALSE;
     863             : #endif
     864             : 
     865           0 :     if (EQUAL(pszCap, OLCIgnoreFields))
     866           0 :         return TRUE;
     867             : 
     868           0 :     if (EQUAL(pszCap, OLCRandomRead))
     869           0 :         return pszFIDColumn != nullptr;
     870           0 :     else if (EQUAL(pszCap, OLCFastFeatureCount))
     871           0 :         return TRUE;
     872           0 :     else if (EQUAL(pszCap, OLCCurveGeometries))
     873           0 :         return TRUE;
     874           0 :     else if (EQUAL(pszCap, OLCMeasuredGeometries))
     875           0 :         return TRUE;
     876           0 :     else if (EQUAL(pszCap, OLCZGeometries))
     877           0 :         return TRUE;
     878             :     else
     879           0 :         return OGRMSSQLSpatialLayer::TestCapability(pszCap);
     880             : }
     881             : 
     882             : /************************************************************************/
     883             : /*                          GetFeatureCount()                           */
     884             : /************************************************************************/
     885             : 
     886           0 : GIntBig OGRMSSQLSpatialTableLayer::GetFeatureCount(int bForce)
     887             : 
     888             : {
     889           0 :     poDS->EndCopy();
     890             : 
     891           0 :     GetLayerDefn();
     892             : 
     893           0 :     if (TestCapability(OLCFastFeatureCount) == FALSE)
     894           0 :         return OGRMSSQLSpatialLayer::GetFeatureCount(bForce);
     895             : 
     896           0 :     CPLODBCStatement *poStatement = BuildStatement("count(*)");
     897             : 
     898           0 :     if (poStatement == nullptr || !poStatement->Fetch())
     899             :     {
     900           0 :         delete poStatement;
     901           0 :         return OGRMSSQLSpatialLayer::GetFeatureCount(bForce);
     902             :     }
     903             : 
     904           0 :     GIntBig nRet = CPLAtoGIntBig(poStatement->GetColData(0));
     905           0 :     delete poStatement;
     906           0 :     return nRet;
     907             : }
     908             : 
     909             : /************************************************************************/
     910             : /*                             StartCopy()                              */
     911             : /************************************************************************/
     912             : 
     913           0 : OGRErr OGRMSSQLSpatialTableLayer::StartCopy()
     914             : 
     915             : {
     916           0 :     return OGRERR_NONE;
     917             : }
     918             : 
     919             : /************************************************************************/
     920             : /*                              EndCopy()                               */
     921             : /************************************************************************/
     922             : 
     923           0 : OGRErr OGRMSSQLSpatialTableLayer::EndCopy()
     924             : 
     925             : {
     926             : #ifdef MSSQL_BCP_SUPPORTED
     927             :     CloseBCP();
     928             : #endif
     929           0 :     return OGRERR_NONE;
     930             : }
     931             : 
     932             : /************************************************************************/
     933             : /*                            CreateField()                             */
     934             : /************************************************************************/
     935             : 
     936           0 : OGRErr OGRMSSQLSpatialTableLayer::CreateField(const OGRFieldDefn *poFieldIn,
     937             :                                               int bApproxOK)
     938             : 
     939             : {
     940             :     char szFieldType[256];
     941           0 :     OGRFieldDefn oField(poFieldIn);
     942             : 
     943           0 :     poDS->EndCopy();
     944             : 
     945           0 :     GetLayerDefn();
     946             : 
     947             :     /* -------------------------------------------------------------------- */
     948             :     /*      Do we want to "launder" the column names into MSSQL             */
     949             :     /*      friendly format?                                                */
     950             :     /* -------------------------------------------------------------------- */
     951           0 :     if (bLaunderColumnNames)
     952             :     {
     953           0 :         char *pszSafeName = poDS->LaunderName(oField.GetNameRef());
     954             : 
     955           0 :         oField.SetName(pszSafeName);
     956           0 :         CPLFree(pszSafeName);
     957             :     }
     958             : 
     959             :     /* -------------------------------------------------------------------- */
     960             :     /*      Identify the MSSQL type.                                        */
     961             :     /* -------------------------------------------------------------------- */
     962             : 
     963           0 :     if (oField.GetType() == OFTInteger)
     964             :     {
     965           0 :         if (oField.GetWidth() > 0 && bPreservePrecision)
     966           0 :             snprintf(szFieldType, sizeof(szFieldType), "numeric(%d,0)",
     967             :                      oField.GetWidth());
     968           0 :         else if (oField.GetSubType() == OFSTInt16)
     969           0 :             strcpy(szFieldType, "smallint");
     970             :         else
     971           0 :             strcpy(szFieldType, "int");
     972             :     }
     973           0 :     else if (oField.GetType() == OFTInteger64)
     974             :     {
     975           0 :         if (oField.GetWidth() > 0 && bPreservePrecision)
     976           0 :             snprintf(szFieldType, sizeof(szFieldType), "numeric(%d,0)",
     977             :                      oField.GetWidth());
     978             :         else
     979           0 :             strcpy(szFieldType, "bigint");
     980             :     }
     981           0 :     else if (oField.GetType() == OFTReal)
     982             :     {
     983           0 :         if (oField.GetWidth() > 0 && oField.GetPrecision() >= 0 &&
     984           0 :             bPreservePrecision)
     985           0 :             snprintf(szFieldType, sizeof(szFieldType), "numeric(%d,%d)",
     986             :                      oField.GetWidth(), oField.GetPrecision());
     987           0 :         else if (oField.GetSubType() == OFSTFloat32)
     988           0 :             strcpy(szFieldType, "float(23)");
     989             :         else
     990           0 :             strcpy(szFieldType, "float(53)");
     991             :     }
     992           0 :     else if (oField.GetType() == OFTString)
     993             :     {
     994           0 :         if (oField.GetSubType() == OGRFieldSubType::OFSTUUID)
     995             :         {
     996           0 :             m_bHasUUIDColumn = true;
     997           0 :             strcpy(szFieldType, "uniqueidentifier");
     998             :         }
     999           0 :         else if (oField.GetWidth() == 0 || oField.GetWidth() > 4000 ||
    1000           0 :                  !bPreservePrecision)
    1001           0 :             strcpy(szFieldType, "nvarchar(MAX)");
    1002             :         else
    1003           0 :             snprintf(szFieldType, sizeof(szFieldType), "nvarchar(%d)",
    1004             :                      oField.GetWidth());
    1005             :     }
    1006           0 :     else if (oField.GetType() == OFTDate)
    1007             :     {
    1008           0 :         strcpy(szFieldType, "date");
    1009             :     }
    1010           0 :     else if (oField.GetType() == OFTTime)
    1011             :     {
    1012           0 :         strcpy(szFieldType, "time(7)");
    1013             :     }
    1014           0 :     else if (oField.GetType() == OFTDateTime)
    1015             :     {
    1016           0 :         strcpy(szFieldType, "datetime");
    1017             :     }
    1018           0 :     else if (oField.GetType() == OFTBinary)
    1019             :     {
    1020           0 :         strcpy(szFieldType, "image");
    1021             :     }
    1022           0 :     else if (bApproxOK)
    1023             :     {
    1024           0 :         CPLError(CE_Warning, CPLE_NotSupported,
    1025             :                  "Can't create field %s with type %s on MSSQL layers.  "
    1026             :                  "Creating as varchar.",
    1027             :                  oField.GetNameRef(),
    1028             :                  OGRFieldDefn::GetFieldTypeName(oField.GetType()));
    1029           0 :         strcpy(szFieldType, "varchar");
    1030             :     }
    1031             :     else
    1032             :     {
    1033           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1034             :                  "Can't create field %s with type %s on MSSQL layers.",
    1035             :                  oField.GetNameRef(),
    1036             :                  OGRFieldDefn::GetFieldTypeName(oField.GetType()));
    1037             : 
    1038           0 :         return OGRERR_FAILURE;
    1039             :     }
    1040             : 
    1041             :     /* -------------------------------------------------------------------- */
    1042             :     /*      Create the new field.                                           */
    1043             :     /* -------------------------------------------------------------------- */
    1044             : 
    1045           0 :     CPLODBCStatement oStmt(poDS->GetSession());
    1046             : 
    1047           0 :     oStmt.Appendf("ALTER TABLE [%s].[%s] ADD [%s] %s", pszSchemaName,
    1048             :                   pszTableName, oField.GetNameRef(), szFieldType);
    1049             : 
    1050           0 :     if (!oField.IsNullable())
    1051             :     {
    1052           0 :         oStmt.Append(" NOT NULL");
    1053             :     }
    1054           0 :     if (oField.GetDefault() != nullptr && !oField.IsDefaultDriverSpecific())
    1055             :     {
    1056             :         /* process default value specifications */
    1057           0 :         if (EQUAL(oField.GetDefault(), "CURRENT_TIME"))
    1058           0 :             oStmt.Append(" DEFAULT(CONVERT([time],getdate()))");
    1059           0 :         else if (EQUAL(oField.GetDefault(), "CURRENT_DATE"))
    1060           0 :             oStmt.Append(" DEFAULT(CONVERT([date],getdate()))");
    1061             :         else
    1062           0 :             oStmt.Appendf(" DEFAULT(%s)", oField.GetDefault());
    1063             :     }
    1064             : 
    1065           0 :     if (!oStmt.ExecuteSQL())
    1066             :     {
    1067           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error creating field %s, %s",
    1068           0 :                  oField.GetNameRef(), poDS->GetSession()->GetLastError());
    1069             : 
    1070           0 :         return OGRERR_FAILURE;
    1071             :     }
    1072             : 
    1073             :     /* -------------------------------------------------------------------- */
    1074             :     /*      Add the field to the OGRFeatureDefn.                            */
    1075             :     /* -------------------------------------------------------------------- */
    1076             : 
    1077           0 :     poFeatureDefn->AddFieldDefn(&oField);
    1078             : 
    1079           0 :     return OGRERR_NONE;
    1080             : }
    1081             : 
    1082             : /************************************************************************/
    1083             : /*                             ISetFeature()                             */
    1084             : /*                                                                      */
    1085             : /*      SetFeature() is implemented by an UPDATE SQL command            */
    1086             : /************************************************************************/
    1087             : 
    1088           0 : OGRErr OGRMSSQLSpatialTableLayer::ISetFeature(OGRFeature *poFeature)
    1089             : 
    1090             : {
    1091           0 :     if (!bUpdateAccess)
    1092             :     {
    1093           0 :         CPLError(CE_Failure, CPLE_NotSupported, UNSUPPORTED_OP_READ_ONLY,
    1094             :                  "SetFeature");
    1095           0 :         return OGRERR_FAILURE;
    1096             :     }
    1097             : 
    1098           0 :     OGRErr eErr = OGRERR_FAILURE;
    1099             : 
    1100           0 :     poDS->EndCopy();
    1101             : 
    1102           0 :     GetLayerDefn();
    1103             : 
    1104           0 :     if (nullptr == poFeature)
    1105             :     {
    1106           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1107             :                  "NULL pointer to OGRFeature passed to SetFeature().");
    1108           0 :         return eErr;
    1109             :     }
    1110             : 
    1111           0 :     if (poFeature->GetFID() == OGRNullFID)
    1112             :     {
    1113           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1114             :                  "FID required on features given to SetFeature().");
    1115           0 :         return eErr;
    1116             :     }
    1117             : 
    1118           0 :     if (!pszFIDColumn)
    1119             :     {
    1120           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1121             :                  "Unable to update features in tables without\n"
    1122             :                  "a recognised FID column.");
    1123           0 :         return eErr;
    1124             :     }
    1125             : 
    1126           0 :     ClearStatement();
    1127             : 
    1128             :     /* -------------------------------------------------------------------- */
    1129             :     /*      Form the UPDATE command.                                        */
    1130             :     /* -------------------------------------------------------------------- */
    1131           0 :     CPLODBCStatement oStmt(poDS->GetSession());
    1132             : 
    1133           0 :     oStmt.Appendf("UPDATE [%s].[%s] SET ", pszSchemaName, pszTableName);
    1134             : 
    1135           0 :     OGRGeometry *poGeom = poFeature->GetGeometryRef();
    1136           0 :     if (bUseGeometryValidation && poGeom != nullptr)
    1137             :     {
    1138           0 :         OGRMSSQLGeometryValidator oValidator(poGeom, nGeomColumnType);
    1139           0 :         if (!oValidator.IsValid())
    1140             :         {
    1141           0 :             oValidator.MakeValid(poGeom);
    1142           0 :             CPLError(CE_Warning, CPLE_NotSupported,
    1143             :                      "Geometry with FID = " CPL_FRMT_GIB
    1144             :                      " has been modified to valid geometry.",
    1145             :                      poFeature->GetFID());
    1146             :         }
    1147             :     }
    1148             : 
    1149           0 :     int nFieldCount = poFeatureDefn->GetFieldCount();
    1150           0 :     int bind_num = 0;
    1151           0 :     void **bind_buffer = (void **)CPLMalloc(sizeof(void *) * nFieldCount);
    1152             : 
    1153           0 :     int bNeedComma = FALSE;
    1154             :     SQLLEN nWKBLenBindParameter;
    1155           0 :     if (poGeom != nullptr && pszGeomColumn != nullptr)
    1156             :     {
    1157           0 :         oStmt.Appendf("[%s] = ", pszGeomColumn);
    1158             : 
    1159           0 :         if (nUploadGeometryFormat == MSSQLGEOMETRY_NATIVE)
    1160             :         {
    1161           0 :             OGRMSSQLGeometryWriter poWriter(poGeom, nGeomColumnType, nSRSId);
    1162           0 :             int nDataLen = poWriter.GetDataLen();
    1163           0 :             GByte *pabyData = (GByte *)CPLMalloc(nDataLen + 1);
    1164           0 :             if (poWriter.WriteSqlGeometry(pabyData, nDataLen) == OGRERR_NONE)
    1165             :             {
    1166           0 :                 char *pszBytes = GByteArrayToHexString(pabyData, nDataLen);
    1167           0 :                 SQLLEN nts = SQL_NTS;
    1168           0 :                 int nRetCode = SQLBindParameter(
    1169           0 :                     oStmt.GetStatement(), (SQLUSMALLINT)(bind_num + 1),
    1170             :                     SQL_PARAM_INPUT, SQL_C_CHAR, SQL_LONGVARCHAR, nDataLen, 0,
    1171           0 :                     (SQLPOINTER)pszBytes, 0, &nts);
    1172           0 :                 if (nRetCode == SQL_SUCCESS ||
    1173             :                     nRetCode == SQL_SUCCESS_WITH_INFO)
    1174             :                 {
    1175           0 :                     oStmt.Append("?");
    1176           0 :                     bind_buffer[bind_num] = pszBytes;
    1177           0 :                     ++bind_num;
    1178             :                 }
    1179             :                 else
    1180             :                 {
    1181           0 :                     oStmt.Append("null");
    1182           0 :                     CPLFree(pszBytes);
    1183             :                 }
    1184             :             }
    1185             :             else
    1186             :             {
    1187           0 :                 oStmt.Append("null");
    1188             :             }
    1189           0 :             CPLFree(pabyData);
    1190             :         }
    1191           0 :         else if (nUploadGeometryFormat == MSSQLGEOMETRY_WKB)
    1192             :         {
    1193           0 :             const size_t nWKBLen = poGeom->WkbSize();
    1194           0 :             GByte *pabyWKB = (GByte *)VSI_MALLOC_VERBOSE(
    1195             :                 nWKBLen + 1);  // do we need the +1 ?
    1196           0 :             if (pabyWKB == nullptr)
    1197             :             {
    1198           0 :                 oStmt.Append("null");
    1199             :             }
    1200           0 :             else if (poGeom->exportToWkb(wkbNDR, pabyWKB, wkbVariantIso) ==
    1201           0 :                          OGRERR_NONE &&
    1202           0 :                      (nGeomColumnType == MSSQLCOLTYPE_GEOMETRY ||
    1203           0 :                       nGeomColumnType == MSSQLCOLTYPE_GEOGRAPHY))
    1204             :             {
    1205           0 :                 nWKBLenBindParameter = nWKBLen;
    1206           0 :                 int nRetCode = SQLBindParameter(
    1207           0 :                     oStmt.GetStatement(), (SQLUSMALLINT)(bind_num + 1),
    1208             :                     SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, nWKBLen,
    1209           0 :                     0, (SQLPOINTER)pabyWKB, nWKBLen, &nWKBLenBindParameter);
    1210           0 :                 if (nRetCode == SQL_SUCCESS ||
    1211             :                     nRetCode == SQL_SUCCESS_WITH_INFO)
    1212             :                 {
    1213           0 :                     if (nGeomColumnType == MSSQLCOLTYPE_GEOGRAPHY)
    1214             :                     {
    1215           0 :                         oStmt.Append("geography::STGeomFromWKB(?");
    1216           0 :                         oStmt.Appendf(",%d)", nSRSId);
    1217             :                     }
    1218             :                     else
    1219             :                     {
    1220           0 :                         oStmt.Append("geometry::STGeomFromWKB(?");
    1221           0 :                         oStmt.Appendf(",%d).MakeValid()", nSRSId);
    1222             :                     }
    1223           0 :                     bind_buffer[bind_num] = pabyWKB;
    1224           0 :                     ++bind_num;
    1225             :                 }
    1226             :                 else
    1227             :                 {
    1228           0 :                     oStmt.Append("null");
    1229           0 :                     CPLFree(pabyWKB);
    1230             :                 }
    1231             :             }
    1232             :             else
    1233             :             {
    1234           0 :                 oStmt.Append("null");
    1235           0 :                 CPLFree(pabyWKB);
    1236             :             }
    1237             :         }
    1238           0 :         else if (nUploadGeometryFormat == MSSQLGEOMETRY_WKT)
    1239             :         {
    1240           0 :             char *pszWKT = nullptr;
    1241           0 :             if (poGeom->exportToWkt(&pszWKT) == OGRERR_NONE &&
    1242           0 :                 (nGeomColumnType == MSSQLCOLTYPE_GEOMETRY ||
    1243           0 :                  nGeomColumnType == MSSQLCOLTYPE_GEOGRAPHY))
    1244             :             {
    1245           0 :                 size_t nLen = 0;
    1246           0 :                 while (pszWKT[nLen] != '\0')
    1247           0 :                     nLen++;
    1248             : 
    1249           0 :                 int nRetCode = SQLBindParameter(
    1250           0 :                     oStmt.GetStatement(), (SQLUSMALLINT)(bind_num + 1),
    1251             :                     SQL_PARAM_INPUT, SQL_C_CHAR, SQL_LONGVARCHAR, nLen, 0,
    1252           0 :                     (SQLPOINTER)pszWKT, 0, nullptr);
    1253           0 :                 if (nRetCode == SQL_SUCCESS ||
    1254             :                     nRetCode == SQL_SUCCESS_WITH_INFO)
    1255             :                 {
    1256           0 :                     if (nGeomColumnType == MSSQLCOLTYPE_GEOGRAPHY)
    1257             :                     {
    1258           0 :                         oStmt.Append("geography::STGeomFromText(?");
    1259           0 :                         oStmt.Appendf(",%d)", nSRSId);
    1260             :                     }
    1261             :                     else
    1262             :                     {
    1263           0 :                         oStmt.Append("geometry::STGeomFromText(?");
    1264           0 :                         oStmt.Appendf(",%d).MakeValid()", nSRSId);
    1265             :                     }
    1266           0 :                     bind_buffer[bind_num] = pszWKT;
    1267           0 :                     ++bind_num;
    1268             :                 }
    1269             :                 else
    1270             :                 {
    1271           0 :                     oStmt.Append("null");
    1272           0 :                     CPLFree(pszWKT);
    1273             :                 }
    1274             :             }
    1275             :             else
    1276             :             {
    1277           0 :                 oStmt.Append("null");
    1278           0 :                 CPLFree(pszWKT);
    1279             :             }
    1280             :         }
    1281             :         else
    1282           0 :             oStmt.Append("null");
    1283             : 
    1284           0 :         bNeedComma = TRUE;
    1285             :     }
    1286             : 
    1287             :     int i;
    1288           0 :     for (i = 0; i < nFieldCount; i++)
    1289             :     {
    1290           0 :         if (bNeedComma)
    1291           0 :             oStmt.Appendf(", [%s] = ",
    1292           0 :                           poFeatureDefn->GetFieldDefn(i)->GetNameRef());
    1293             :         else
    1294             :         {
    1295           0 :             oStmt.Appendf("[%s] = ",
    1296           0 :                           poFeatureDefn->GetFieldDefn(i)->GetNameRef());
    1297           0 :             bNeedComma = TRUE;
    1298             :         }
    1299             : 
    1300           0 :         if (!poFeature->IsFieldSetAndNotNull(i))
    1301           0 :             oStmt.Append("null");
    1302             :         else
    1303           0 :             AppendFieldValue(&oStmt, poFeature, i, &bind_num, bind_buffer);
    1304             :     }
    1305             : 
    1306             :     /* Add the WHERE clause */
    1307           0 :     oStmt.Appendf(" WHERE [%s] = " CPL_FRMT_GIB, pszFIDColumn,
    1308             :                   poFeature->GetFID());
    1309             : 
    1310             :     /* -------------------------------------------------------------------- */
    1311             :     /*      Execute the update.                                             */
    1312             :     /* -------------------------------------------------------------------- */
    1313             : 
    1314           0 :     if (!oStmt.ExecuteSQL())
    1315             :     {
    1316           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1317             :                  "Error updating feature with FID:" CPL_FRMT_GIB ", %s",
    1318           0 :                  poFeature->GetFID(), poDS->GetSession()->GetLastError());
    1319             : 
    1320           0 :         for (i = 0; i < bind_num; i++)
    1321           0 :             CPLFree(bind_buffer[i]);
    1322           0 :         CPLFree(bind_buffer);
    1323             : 
    1324           0 :         return OGRERR_FAILURE;
    1325             :     }
    1326             : 
    1327           0 :     for (i = 0; i < bind_num; i++)
    1328           0 :         CPLFree(bind_buffer[i]);
    1329           0 :     CPLFree(bind_buffer);
    1330             : 
    1331           0 :     if (oStmt.GetRowCountAffected() < 1)
    1332           0 :         return OGRERR_NON_EXISTING_FEATURE;
    1333             : 
    1334           0 :     return OGRERR_NONE;
    1335             : }
    1336             : 
    1337             : /************************************************************************/
    1338             : /*                          DeleteFeature()                             */
    1339             : /************************************************************************/
    1340             : 
    1341           0 : OGRErr OGRMSSQLSpatialTableLayer::DeleteFeature(GIntBig nFID)
    1342             : 
    1343             : {
    1344           0 :     if (!bUpdateAccess)
    1345             :     {
    1346           0 :         CPLError(CE_Failure, CPLE_NotSupported, UNSUPPORTED_OP_READ_ONLY,
    1347             :                  "DeleteFeature");
    1348           0 :         return OGRERR_FAILURE;
    1349             :     }
    1350             : 
    1351           0 :     poDS->EndCopy();
    1352             : 
    1353           0 :     GetLayerDefn();
    1354             : 
    1355           0 :     if (pszFIDColumn == nullptr)
    1356             :     {
    1357           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1358             :                  "DeleteFeature() without any FID column.");
    1359           0 :         return OGRERR_FAILURE;
    1360             :     }
    1361             : 
    1362           0 :     if (nFID == OGRNullFID)
    1363             :     {
    1364           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1365             :                  "DeleteFeature() with unset FID fails.");
    1366           0 :         return OGRERR_FAILURE;
    1367             :     }
    1368             : 
    1369           0 :     ClearStatement();
    1370             : 
    1371             :     /* -------------------------------------------------------------------- */
    1372             :     /*      Drop the record with this FID.                                  */
    1373             :     /* -------------------------------------------------------------------- */
    1374           0 :     CPLODBCStatement oStatement(poDS->GetSession());
    1375             : 
    1376           0 :     oStatement.Appendf("DELETE FROM [%s].[%s] WHERE [%s] = " CPL_FRMT_GIB,
    1377             :                        pszSchemaName, pszTableName, pszFIDColumn, nFID);
    1378             : 
    1379           0 :     if (!oStatement.ExecuteSQL())
    1380             :     {
    1381           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1382             :                  "Attempt to delete feature with FID " CPL_FRMT_GIB
    1383             :                  " failed. %s",
    1384           0 :                  nFID, poDS->GetSession()->GetLastError());
    1385             : 
    1386           0 :         return OGRERR_FAILURE;
    1387             :     }
    1388             : 
    1389           0 :     if (oStatement.GetRowCountAffected() < 1)
    1390           0 :         return OGRERR_NON_EXISTING_FEATURE;
    1391             : 
    1392           0 :     return OGRERR_NONE;
    1393             : }
    1394             : 
    1395             : /************************************************************************/
    1396             : /*                           Failed()                                   */
    1397             : /************************************************************************/
    1398             : 
    1399           0 : int OGRMSSQLSpatialTableLayer::Failed(int nRetCode)
    1400             : 
    1401             : {
    1402           0 :     if (nRetCode == SQL_SUCCESS || nRetCode == SQL_SUCCESS_WITH_INFO)
    1403           0 :         return FALSE;
    1404             : 
    1405           0 :     char SQLState[6] = "";
    1406           0 :     char Msg[256] = "";
    1407           0 :     SQLINTEGER iNativeError = 0;
    1408           0 :     SQLSMALLINT iMsgLen = 0;
    1409             : 
    1410           0 :     int iRc = SQLGetDiagRec(SQL_HANDLE_ENV, hEnvBCP, 1, (SQLCHAR *)SQLState,
    1411           0 :                             &iNativeError, (SQLCHAR *)Msg, 256, &iMsgLen);
    1412           0 :     if (iRc != SQL_NO_DATA)
    1413             :     {
    1414           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1415             :                  "SQL Error SQLState=%s, NativeError=%d, Msg=%s\n", SQLState,
    1416             :                  static_cast<int>(iNativeError), Msg);
    1417             :     }
    1418             : 
    1419           0 :     return TRUE;
    1420             : }
    1421             : 
    1422             : /************************************************************************/
    1423             : /*                           Failed2()                                  */
    1424             : /************************************************************************/
    1425             : 
    1426             : #ifdef MSSQL_BCP_SUPPORTED
    1427             : int OGRMSSQLSpatialTableLayer::Failed2(int nRetCode)
    1428             : 
    1429             : {
    1430             :     if (nRetCode == SUCCEED)
    1431             :         return FALSE;
    1432             : 
    1433             :     char SQLState[6] = "";
    1434             :     char Msg[256] = "";
    1435             :     SQLINTEGER iNativeError = 0;
    1436             :     SQLSMALLINT iMsgLen = 0;
    1437             : 
    1438             :     int iRc = SQLGetDiagRec(SQL_HANDLE_DBC, hDBCBCP, 1, (SQLCHAR *)SQLState,
    1439             :                             &iNativeError, (SQLCHAR *)Msg, 256, &iMsgLen);
    1440             :     if (iRc != SQL_NO_DATA)
    1441             :     {
    1442             :         CPLError(CE_Failure, CPLE_AppDefined,
    1443             :                  "SQL Error SQLState=%s, NativeError=%d, Msg=%s\n", SQLState,
    1444             :                  static_cast<int>(iNativeError), Msg);
    1445             :     }
    1446             : 
    1447             :     return TRUE;
    1448             : }
    1449             : 
    1450             : /************************************************************************/
    1451             : /*                            InitBCP()                                 */
    1452             : /************************************************************************/
    1453             : 
    1454             : int OGRMSSQLSpatialTableLayer::InitBCP(const char *pszDSN)
    1455             : 
    1456             : {
    1457             :     /* Create a different connection for BCP upload */
    1458             :     if (Failed(SQLAllocHandle(SQL_HANDLE_ENV, nullptr, &hEnvBCP)))
    1459             :         return FALSE;
    1460             : 
    1461             :     /* Notify ODBC that this is an ODBC 3.0 app. */
    1462             :     if (Failed(SQLSetEnvAttr(hEnvBCP, SQL_ATTR_ODBC_VERSION,
    1463             :                              (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER)))
    1464             :     {
    1465             :         CloseBCP();
    1466             :         return FALSE;
    1467             :     }
    1468             : 
    1469             :     if (Failed(SQLAllocHandle(SQL_HANDLE_DBC, hEnvBCP, &hDBCBCP)))
    1470             :     {
    1471             :         CloseBCP();
    1472             :         return FALSE;
    1473             :     }
    1474             : 
    1475             :     /* set bulk copy mode */
    1476             :     if (Failed(SQLSetConnectAttr(hDBCBCP, SQL_COPT_SS_BCP, (void *)SQL_BCP_ON,
    1477             :                                  SQL_IS_INTEGER)))
    1478             :     {
    1479             :         CloseBCP();
    1480             :         return FALSE;
    1481             :     }
    1482             : 
    1483             :     Failed(SQLSetConnectAttr(hDBCBCP, SQL_ATTR_LOGIN_TIMEOUT, (void *)30,
    1484             :                              SQL_IS_INTEGER));
    1485             : 
    1486             :     SQLCHAR szOutConnString[1024];
    1487             :     SQLSMALLINT nOutConnStringLen = 0;
    1488             : 
    1489             :     if (Failed(SQLDriverConnect(hDBCBCP, nullptr, (SQLCHAR *)pszDSN,
    1490             :                                 (SQLSMALLINT)strlen(pszDSN), szOutConnString,
    1491             :                                 sizeof(szOutConnString), &nOutConnStringLen,
    1492             :                                 SQL_DRIVER_NOPROMPT)))
    1493             :     {
    1494             :         CloseBCP();
    1495             :         return FALSE;
    1496             :     }
    1497             : 
    1498             :     return TRUE;
    1499             : }
    1500             : 
    1501             : /************************************************************************/
    1502             : /*                            CloseBCP()                                */
    1503             : /************************************************************************/
    1504             : 
    1505             : void OGRMSSQLSpatialTableLayer::CloseBCP()
    1506             : 
    1507             : {
    1508             :     if (papstBindBuffer)
    1509             :     {
    1510             :         int iCol;
    1511             : 
    1512             :         int nRecNum = bcp_done(hDBCBCP);
    1513             :         if (nRecNum == -1)
    1514             :             Failed2(nRecNum);
    1515             : 
    1516             :         for (iCol = 0; iCol < nRawColumns; iCol++)
    1517             :             CPLFree(papstBindBuffer[iCol]);
    1518             :         CPLFree(papstBindBuffer);
    1519             :         papstBindBuffer = nullptr;
    1520             : 
    1521             :         if (bIdentityInsert)
    1522             :         {
    1523             :             bIdentityInsert = FALSE;
    1524             :         }
    1525             :     }
    1526             : 
    1527             :     if (hDBCBCP != nullptr)
    1528             :     {
    1529             :         CPLDebug("ODBC", "SQLDisconnect()");
    1530             :         SQLDisconnect(hDBCBCP);
    1531             :         SQLFreeHandle(SQL_HANDLE_DBC, hDBCBCP);
    1532             :         hDBCBCP = nullptr;
    1533             :     }
    1534             : 
    1535             :     if (hEnvBCP != nullptr)
    1536             :     {
    1537             :         SQLFreeHandle(SQL_HANDLE_ENV, hEnvBCP);
    1538             :         hEnvBCP = nullptr;
    1539             :     }
    1540             : }
    1541             : 
    1542             : /************************************************************************/
    1543             : /*                            CreateFeatureBCP()                        */
    1544             : /************************************************************************/
    1545             : 
    1546             : OGRErr OGRMSSQLSpatialTableLayer::CreateFeatureBCP(OGRFeature *poFeature)
    1547             : 
    1548             : {
    1549             :     int iCol;
    1550             :     int iField = 0;
    1551             : 
    1552             :     if (hDBCBCP == nullptr)
    1553             :     {
    1554             :         nBCPCount = 0;
    1555             : 
    1556             :         /* Tell the datasource we are now planning to copy data */
    1557             :         poDS->StartCopy(this);
    1558             : 
    1559             :         CPLODBCSession *poSession = poDS->GetSession();
    1560             : 
    1561             :         if (poSession->IsInTransaction())
    1562             :             poSession->CommitTransaction(); /* commit creating the table */
    1563             : 
    1564             :         /* Get the column definitions for this table. */
    1565             :         bLayerDefnNeedsRefresh = true;
    1566             :         GetLayerDefn();
    1567             :         bLayerDefnNeedsRefresh = false;
    1568             : 
    1569             :         if (!poFeatureDefn)
    1570             :             return OGRERR_FAILURE;
    1571             : 
    1572             :         if (poFeature->GetFID() != OGRNullFID && pszFIDColumn != nullptr &&
    1573             :             bIsIdentityFid)
    1574             :         {
    1575             :             bIdentityInsert = TRUE;
    1576             :         }
    1577             : 
    1578             :         if (!InitBCP(poDS->GetConnectionString()))
    1579             :             return OGRERR_FAILURE;
    1580             : 
    1581             :         /* Initialize the bulk copy */
    1582             :         if (Failed2(bcp_init(
    1583             :                 hDBCBCP, CPLSPrintf("[%s].[%s]", pszSchemaName, pszTableName),
    1584             :                 nullptr, nullptr, DB_IN)))
    1585             :         {
    1586             :             CloseBCP();
    1587             :             return OGRERR_FAILURE;
    1588             :         }
    1589             : 
    1590             :         if (bIdentityInsert)
    1591             :         {
    1592             :             if (Failed2(bcp_control(hDBCBCP, BCPKEEPIDENTITY, (void *)TRUE)))
    1593             :             {
    1594             :                 CPLError(CE_Failure, CPLE_AppDefined,
    1595             :                          "Failed to set identity insert bulk copy mode, %s.",
    1596             :                          poDS->GetSession()->GetLastError());
    1597             :                 return OGRERR_FAILURE;
    1598             :             }
    1599             :         }
    1600             : 
    1601             :         papstBindBuffer =
    1602             :             (BCPData **)CPLMalloc(sizeof(BCPData *) * (nRawColumns));
    1603             : 
    1604             :         for (iCol = 0; iCol < nRawColumns; iCol++)
    1605             :         {
    1606             :             papstBindBuffer[iCol] = nullptr;
    1607             : 
    1608             :             if (iCol == nGeomColumnIndex)
    1609             :             {
    1610             :                 papstBindBuffer[iCol] = (BCPData *)CPLMalloc(sizeof(BCPData));
    1611             :                 if (Failed2(bcp_bind(hDBCBCP,
    1612             :                                      nullptr /* data is provided later */, 0,
    1613             :                                      0 /*or any value < 8000*/, nullptr, 0,
    1614             :                                      SQLUDT, iCol + 1)))
    1615             :                     return OGRERR_FAILURE;
    1616             :             }
    1617             :             else if (iCol == nFIDColumnIndex)
    1618             :             {
    1619             :                 if (!bIdentityInsert)
    1620             :                     continue;
    1621             :                 /* bind fid column */
    1622             :                 papstBindBuffer[iCol] = (BCPData *)CPLMalloc(sizeof(BCPData));
    1623             :                 papstBindBuffer[iCol]->VarChar.nSize = SQL_VARLEN_DATA;
    1624             : 
    1625             :                 if (Failed2(bcp_bind(
    1626             :                         hDBCBCP, (LPCBYTE)papstBindBuffer[iCol]->VarChar.pData,
    1627             :                         0, SQL_VARLEN_DATA, (LPCBYTE) "", 1, SQLVARCHAR,
    1628             :                         iCol + 1)))
    1629             :                     return OGRERR_FAILURE;
    1630             :             }
    1631             :             else if (iField < poFeatureDefn->GetFieldCount() &&
    1632             :                      iCol == panFieldOrdinals[iField])
    1633             :             {
    1634             :                 OGRFieldDefn *poFDefn = poFeatureDefn->GetFieldDefn(iField);
    1635             : 
    1636             :                 if (poFDefn->IsIgnored())
    1637             :                 {
    1638             :                     /* set null */
    1639             :                     ++iField;
    1640             :                     continue;
    1641             :                 }
    1642             : 
    1643             :                 int iSrcField = poFeature->GetFieldIndex(poFDefn->GetNameRef());
    1644             :                 if (iSrcField < 0)
    1645             :                 {
    1646             :                     ++iField;
    1647             :                     continue; /* no such field at the source */
    1648             :                 }
    1649             : 
    1650             :                 if (poFDefn->GetType() == OFTInteger)
    1651             :                 {
    1652             :                     /* int */
    1653             :                     papstBindBuffer[iCol] =
    1654             :                         (BCPData *)CPLMalloc(sizeof(BCPData));
    1655             :                     papstBindBuffer[iCol]->Integer.iIndicator =
    1656             :                         sizeof(papstBindBuffer[iCol]->Integer.Value);
    1657             : 
    1658             :                     if (Failed2(bcp_bind(
    1659             :                             hDBCBCP, (LPCBYTE)papstBindBuffer[iCol],
    1660             :                             sizeof(papstBindBuffer[iCol]->Integer.iIndicator),
    1661             :                             sizeof(papstBindBuffer[iCol]->Integer.Value),
    1662             :                             nullptr, 0, SQLINT4, iCol + 1)))
    1663             :                         return OGRERR_FAILURE;
    1664             :                 }
    1665             :                 else if (poFDefn->GetType() == OFTInteger64)
    1666             :                 {
    1667             :                     /* bigint */
    1668             :                     papstBindBuffer[iCol] =
    1669             :                         (BCPData *)CPLMalloc(sizeof(BCPData));
    1670             :                     papstBindBuffer[iCol]->VarChar.nSize = SQL_VARLEN_DATA;
    1671             : 
    1672             :                     if (Failed2(bcp_bind(
    1673             :                             hDBCBCP,
    1674             :                             (LPCBYTE)papstBindBuffer[iCol]->VarChar.pData, 0,
    1675             :                             SQL_VARLEN_DATA, (LPCBYTE) "", 1, SQLVARCHAR,
    1676             :                             iCol + 1)))
    1677             :                         return OGRERR_FAILURE;
    1678             :                 }
    1679             :                 else if (poFDefn->GetType() == OFTReal)
    1680             :                 {
    1681             :                     /* float */
    1682             :                     /* TODO convert to DBNUMERIC */
    1683             :                     papstBindBuffer[iCol] =
    1684             :                         (BCPData *)CPLMalloc(sizeof(BCPData));
    1685             :                     papstBindBuffer[iCol]->VarChar.nSize = SQL_VARLEN_DATA;
    1686             : 
    1687             :                     if (Failed2(bcp_bind(
    1688             :                             hDBCBCP,
    1689             :                             (LPCBYTE)papstBindBuffer[iCol]->VarChar.pData, 0,
    1690             :                             SQL_VARLEN_DATA, (LPCBYTE) "", 1, SQLVARCHAR,
    1691             :                             iCol + 1)))
    1692             :                         return OGRERR_FAILURE;
    1693             :                 }
    1694             :                 else if (poFDefn->GetType() == OFTString)
    1695             :                 {
    1696             :                     /* nvarchar */
    1697             :                     papstBindBuffer[iCol] =
    1698             :                         (BCPData *)CPLMalloc(sizeof(BCPData));
    1699             :                     papstBindBuffer[iCol]->VarChar.nSize = poFDefn->GetWidth();
    1700             :                     if (poFDefn->GetWidth() == 0)
    1701             :                     {
    1702             :                         if (Failed2(bcp_bind(
    1703             :                                 hDBCBCP, nullptr /* data is provided later */,
    1704             :                                 0, 0 /*or any value < 8000*/, nullptr, 0, 0,
    1705             :                                 iCol + 1)))
    1706             :                             return OGRERR_FAILURE;
    1707             :                     }
    1708             :                     else
    1709             :                     {
    1710             :                         if (Failed2(bcp_bind(
    1711             :                                 hDBCBCP, (LPCBYTE)papstBindBuffer[iCol],
    1712             :                                 sizeof(papstBindBuffer[iCol]->VarChar.nSize),
    1713             :                                 poFDefn->GetWidth(), nullptr, 0, SQLNVARCHAR,
    1714             :                                 iCol + 1)))
    1715             :                             return OGRERR_FAILURE;
    1716             :                     }
    1717             :                 }
    1718             :                 else if (poFDefn->GetType() == OFTDate)
    1719             :                 {
    1720             :                     /* date */
    1721             :                     papstBindBuffer[iCol] =
    1722             :                         (BCPData *)CPLMalloc(sizeof(BCPData));
    1723             :                     papstBindBuffer[iCol]->VarChar.nSize = SQL_VARLEN_DATA;
    1724             : 
    1725             :                     if (Failed2(bcp_bind(
    1726             :                             hDBCBCP,
    1727             :                             (LPCBYTE)papstBindBuffer[iCol]->VarChar.pData, 0,
    1728             :                             SQL_VARLEN_DATA, (LPCBYTE) "", 1, SQLVARCHAR,
    1729             :                             iCol + 1)))
    1730             :                         return OGRERR_FAILURE;
    1731             :                 }
    1732             :                 else if (poFDefn->GetType() == OFTTime)
    1733             :                 {
    1734             :                     /* time(7) */
    1735             :                     papstBindBuffer[iCol] =
    1736             :                         (BCPData *)CPLMalloc(sizeof(BCPData));
    1737             :                     papstBindBuffer[iCol]->VarChar.nSize = SQL_VARLEN_DATA;
    1738             : 
    1739             :                     if (Failed2(bcp_bind(
    1740             :                             hDBCBCP,
    1741             :                             (LPCBYTE)papstBindBuffer[iCol]->VarChar.pData, 0,
    1742             :                             SQL_VARLEN_DATA, (LPCBYTE) "", 1, SQLVARCHAR,
    1743             :                             iCol + 1)))
    1744             :                         return OGRERR_FAILURE;
    1745             :                 }
    1746             :                 else if (poFDefn->GetType() == OFTDateTime)
    1747             :                 {
    1748             :                     /* datetime */
    1749             :                     papstBindBuffer[iCol] =
    1750             :                         (BCPData *)CPLMalloc(sizeof(BCPData));
    1751             :                     papstBindBuffer[iCol]->VarChar.nSize = SQL_VARLEN_DATA;
    1752             : 
    1753             :                     if (Failed2(bcp_bind(
    1754             :                             hDBCBCP,
    1755             :                             (LPCBYTE)papstBindBuffer[iCol]->VarChar.pData, 0,
    1756             :                             SQL_VARLEN_DATA, (LPCBYTE) "", 1, SQLVARCHAR,
    1757             :                             iCol + 1)))
    1758             :                         return OGRERR_FAILURE;
    1759             :                 }
    1760             :                 else if (poFDefn->GetType() == OFTBinary)
    1761             :                 {
    1762             :                     /* image */
    1763             :                     papstBindBuffer[iCol] =
    1764             :                         (BCPData *)CPLMalloc(sizeof(BCPData));
    1765             :                     if (Failed2(bcp_bind(hDBCBCP,
    1766             :                                          nullptr /* data is provided later */,
    1767             :                                          0, 0 /*or any value < 8000*/, nullptr,
    1768             :                                          0, 0, iCol + 1)))
    1769             :                         return OGRERR_FAILURE;
    1770             :                 }
    1771             :                 else
    1772             :                 {
    1773             :                     CPLError(
    1774             :                         CE_Failure, CPLE_NotSupported,
    1775             :                         "Filed %s with type %s is not supported for bulk "
    1776             :                         "insert.",
    1777             :                         poFDefn->GetNameRef(),
    1778             :                         OGRFieldDefn::GetFieldTypeName(poFDefn->GetType()));
    1779             : 
    1780             :                     return OGRERR_FAILURE;
    1781             :                 }
    1782             : 
    1783             :                 ++iField;
    1784             :             }
    1785             :         }
    1786             :     }
    1787             : 
    1788             :     /* do bulk insert here */
    1789             : 
    1790             :     /* prepare data to variables */
    1791             :     iField = 0;
    1792             :     for (iCol = 0; iCol < nRawColumns; iCol++)
    1793             :     {
    1794             :         if (iCol == nGeomColumnIndex)
    1795             :         {
    1796             :             OGRGeometry *poGeom = poFeature->GetGeometryRef();
    1797             :             if (poGeom != nullptr)
    1798             :             {
    1799             :                 /* prepare geometry */
    1800             :                 if (bUseGeometryValidation)
    1801             :                 {
    1802             :                     OGRMSSQLGeometryValidator oValidator(poGeom,
    1803             :                                                          nGeomColumnType);
    1804             :                     if (!oValidator.IsValid())
    1805             :                     {
    1806             :                         oValidator.MakeValid(poGeom);
    1807             :                         CPLError(CE_Warning, CPLE_NotSupported,
    1808             :                                  "Geometry with FID = " CPL_FRMT_GIB
    1809             :                                  " has been modified to valid geometry.",
    1810             :                                  poFeature->GetFID());
    1811             :                     }
    1812             :                 }
    1813             : 
    1814             :                 int nOutgoingSRSId = 0;
    1815             :                 // Use the SRID specified by the provided feature's geometry, if
    1816             :                 // its spatial-reference system is known; otherwise, use the
    1817             :                 // SRID associated with the table
    1818             :                 const OGRSpatialReference *poFeatureSRS =
    1819             :                     poGeom->getSpatialReference();
    1820             :                 if (poFeatureSRS)
    1821             :                     nOutgoingSRSId = poDS->FetchSRSId(poFeatureSRS);
    1822             :                 if (nOutgoingSRSId <= 0)
    1823             :                     nOutgoingSRSId = nSRSId;
    1824             : 
    1825             :                 OGRMSSQLGeometryWriter poWriter(poGeom, nGeomColumnType,
    1826             :                                                 nOutgoingSRSId);
    1827             :                 papstBindBuffer[iCol]->RawData.nSize = poWriter.GetDataLen();
    1828             :                 papstBindBuffer[iCol]->RawData.pData = (GByte *)CPLMalloc(
    1829             :                     papstBindBuffer[iCol]->RawData.nSize + 1);
    1830             : 
    1831             :                 if (poWriter.WriteSqlGeometry(
    1832             :                         papstBindBuffer[iCol]->RawData.pData,
    1833             :                         (int)papstBindBuffer[iCol]->RawData.nSize) !=
    1834             :                     OGRERR_NONE)
    1835             :                     return OGRERR_FAILURE;
    1836             : 
    1837             :                 /* set data length */
    1838             :                 if (Failed2(bcp_collen(
    1839             :                         hDBCBCP, (DBINT)papstBindBuffer[iCol]->RawData.nSize,
    1840             :                         iCol + 1)))
    1841             :                     return OGRERR_FAILURE;
    1842             :             }
    1843             :             else
    1844             :             {
    1845             :                 /* set NULL */
    1846             :                 papstBindBuffer[iCol]->RawData.nSize = SQL_NULL_DATA;
    1847             :                 if (Failed2(bcp_collen(hDBCBCP, SQL_NULL_DATA, iCol + 1)))
    1848             :                     return OGRERR_FAILURE;
    1849             :             }
    1850             :         }
    1851             :         else if (iCol == nFIDColumnIndex)
    1852             :         {
    1853             :             if (!bIdentityInsert)
    1854             :                 continue;
    1855             : 
    1856             :             GIntBig nFID = poFeature->GetFID();
    1857             :             if (nFID == OGRNullFID)
    1858             :             {
    1859             :                 papstBindBuffer[iCol]->VarChar.nSize = SQL_NULL_DATA;
    1860             :                 /* set NULL */
    1861             :                 if (Failed2(bcp_collen(hDBCBCP, SQL_NULL_DATA, iCol + 1)))
    1862             :                     return OGRERR_FAILURE;
    1863             :             }
    1864             :             else
    1865             :             {
    1866             :                 papstBindBuffer[iCol]->VarChar.nSize = SQL_VARLEN_DATA;
    1867             :                 snprintf((char *)papstBindBuffer[iCol]->VarChar.pData, 8000,
    1868             :                          CPL_FRMT_GIB, nFID);
    1869             : 
    1870             :                 if (Failed2(bcp_collen(hDBCBCP, SQL_VARLEN_DATA, iCol + 1)))
    1871             :                     return OGRERR_FAILURE;
    1872             :             }
    1873             :         }
    1874             :         else if (iField < poFeatureDefn->GetFieldCount() &&
    1875             :                  iCol == panFieldOrdinals[iField])
    1876             :         {
    1877             :             OGRFieldDefn *poFDefn = poFeatureDefn->GetFieldDefn(iField);
    1878             : 
    1879             :             if (papstBindBuffer[iCol] == nullptr)
    1880             :             {
    1881             :                 ++iField;
    1882             :                 continue; /* column requires no data */
    1883             :             }
    1884             : 
    1885             :             if (poFDefn->GetType() == OFTInteger)
    1886             :             {
    1887             :                 /* int */
    1888             :                 if (!poFeature->IsFieldSetAndNotNull(iField))
    1889             :                     papstBindBuffer[iCol]->Integer.iIndicator = SQL_NULL_DATA;
    1890             :                 else
    1891             :                 {
    1892             :                     papstBindBuffer[iCol]->Integer.iIndicator =
    1893             :                         sizeof(papstBindBuffer[iCol]->Integer.Value);
    1894             :                     papstBindBuffer[iCol]->Integer.Value =
    1895             :                         poFeature->GetFieldAsInteger(iField);
    1896             :                 }
    1897             :             }
    1898             :             else if (poFDefn->GetType() == OFTInteger64)
    1899             :             {
    1900             :                 /* bigint */
    1901             :                 if (!poFeature->IsFieldSetAndNotNull(iField))
    1902             :                 {
    1903             :                     papstBindBuffer[iCol]->VarChar.nSize = SQL_NULL_DATA;
    1904             :                     /* set NULL */
    1905             :                     if (Failed2(bcp_collen(hDBCBCP, SQL_NULL_DATA, iCol + 1)))
    1906             :                         return OGRERR_FAILURE;
    1907             :                 }
    1908             :                 else
    1909             :                 {
    1910             :                     papstBindBuffer[iCol]->VarChar.nSize = SQL_VARLEN_DATA;
    1911             :                     snprintf((char *)papstBindBuffer[iCol]->VarChar.pData, 8000,
    1912             :                              "%s", poFeature->GetFieldAsString(iField));
    1913             : 
    1914             :                     if (Failed2(bcp_collen(hDBCBCP, SQL_VARLEN_DATA, iCol + 1)))
    1915             :                         return OGRERR_FAILURE;
    1916             :                 }
    1917             :             }
    1918             :             else if (poFDefn->GetType() == OFTReal)
    1919             :             {
    1920             :                 /* float */
    1921             :                 if (!poFeature->IsFieldSetAndNotNull(iField))
    1922             :                 {
    1923             :                     papstBindBuffer[iCol]->VarChar.nSize = SQL_NULL_DATA;
    1924             :                     /* set NULL */
    1925             :                     if (Failed2(bcp_collen(hDBCBCP, SQL_NULL_DATA, iCol + 1)))
    1926             :                         return OGRERR_FAILURE;
    1927             :                 }
    1928             :                 else
    1929             :                 {
    1930             :                     papstBindBuffer[iCol]->VarChar.nSize = SQL_VARLEN_DATA;
    1931             :                     snprintf((char *)papstBindBuffer[iCol]->VarChar.pData, 8000,
    1932             :                              "%s", poFeature->GetFieldAsString(iField));
    1933             : 
    1934             :                     if (Failed2(bcp_collen(hDBCBCP, SQL_VARLEN_DATA, iCol + 1)))
    1935             :                         return OGRERR_FAILURE;
    1936             :                 }
    1937             :             }
    1938             :             else if (poFDefn->GetType() == OFTString)
    1939             :             {
    1940             :                 /* nvarchar */
    1941             :                 if (poFDefn->GetWidth() != 0)
    1942             :                 {
    1943             :                     if (!poFeature->IsFieldSetAndNotNull(iField))
    1944             :                     {
    1945             :                         papstBindBuffer[iCol]->VarChar.nSize = SQL_NULL_DATA;
    1946             :                         if (Failed2(
    1947             :                                 bcp_collen(hDBCBCP, SQL_NULL_DATA, iCol + 1)))
    1948             :                             return OGRERR_FAILURE;
    1949             :                     }
    1950             :                     else
    1951             :                     {
    1952             : 
    1953             :                         wchar_t *buffer = CPLRecodeToWChar(
    1954             :                             poFeature->GetFieldAsString(iField), CPL_ENC_UTF8,
    1955             :                             CPL_ENC_UCS2);
    1956             :                         const auto nLen = wcslen(buffer);
    1957             :                         papstBindBuffer[iCol]->VarChar.nSize =
    1958             :                             (SQLLEN)nLen * sizeof(GUInt16);
    1959             : #if WCHAR_MAX > 0xFFFFu
    1960             :                         // Shorten each character to a two-byte value, as
    1961             :                         // expected by the ODBC driver
    1962             :                         GUInt16 *panBuffer =
    1963             :                             reinterpret_cast<GUInt16 *>(buffer);
    1964             :                         for (unsigned int nIndex = 1; nIndex <= nLen;
    1965             :                              nIndex += 1)
    1966             :                             panBuffer[nIndex] =
    1967             :                                 static_cast<GUInt16>(buffer[nIndex]);
    1968             : #endif
    1969             :                         memcpy(papstBindBuffer[iCol]->VarChar.pData, buffer,
    1970             :                                papstBindBuffer[iCol]->VarChar.nSize +
    1971             :                                    sizeof(GUInt16));
    1972             :                         CPLFree(buffer);
    1973             : 
    1974             :                         if (Failed2(bcp_collen(
    1975             :                                 hDBCBCP,
    1976             :                                 (DBINT)papstBindBuffer[iCol]->VarChar.nSize,
    1977             :                                 iCol + 1)))
    1978             :                             return OGRERR_FAILURE;
    1979             :                     }
    1980             :                 }
    1981             :             }
    1982             :             else if (poFDefn->GetType() == OFTDate)
    1983             :             {
    1984             :                 /* date */
    1985             :                 if (!poFeature->IsFieldSetAndNotNull(iField))
    1986             :                 {
    1987             :                     papstBindBuffer[iCol]->VarChar.nSize = SQL_NULL_DATA;
    1988             :                     /* set NULL */
    1989             :                     if (Failed2(bcp_collen(hDBCBCP, SQL_NULL_DATA, iCol + 1)))
    1990             :                         return OGRERR_FAILURE;
    1991             :                 }
    1992             :                 else
    1993             :                 {
    1994             :                     int pnYear;
    1995             :                     int pnMonth;
    1996             :                     int pnDay;
    1997             :                     int pnHour;
    1998             :                     int pnMinute;
    1999             :                     float pfSecond;
    2000             :                     int pnTZFlag;
    2001             : 
    2002             :                     poFeature->GetFieldAsDateTime(iField, &pnYear, &pnMonth,
    2003             :                                                   &pnDay, &pnHour, &pnMinute,
    2004             :                                                   &pfSecond, &pnTZFlag);
    2005             : 
    2006             :                     papstBindBuffer[iCol]->VarChar.nSize = SQL_VARLEN_DATA;
    2007             :                     snprintf((char *)papstBindBuffer[iCol]->VarChar.pData, 8000,
    2008             :                              "%4d-%02d-%02d %02d:%02d:%06.3f", pnYear, pnMonth,
    2009             :                              pnDay, pnHour, pnMinute, pfSecond);
    2010             :                     if (Failed2(bcp_collen(hDBCBCP, SQL_VARLEN_DATA, iCol + 1)))
    2011             :                         return OGRERR_FAILURE;
    2012             :                 }
    2013             :             }
    2014             :             else if (poFDefn->GetType() == OFTTime)
    2015             :             {
    2016             :                 /* time(7) */
    2017             :                 if (!poFeature->IsFieldSetAndNotNull(iField))
    2018             :                 {
    2019             :                     papstBindBuffer[iCol]->VarChar.nSize = SQL_NULL_DATA;
    2020             :                     /* set NULL */
    2021             :                     if (Failed2(bcp_collen(hDBCBCP, SQL_NULL_DATA, iCol + 1)))
    2022             :                         return OGRERR_FAILURE;
    2023             :                 }
    2024             :                 else
    2025             :                 {
    2026             :                     int pnYear;
    2027             :                     int pnMonth;
    2028             :                     int pnDay;
    2029             :                     int pnHour;
    2030             :                     int pnMinute;
    2031             :                     float pfSecond;
    2032             :                     int pnTZFlag;
    2033             : 
    2034             :                     poFeature->GetFieldAsDateTime(iField, &pnYear, &pnMonth,
    2035             :                                                   &pnDay, &pnHour, &pnMinute,
    2036             :                                                   &pfSecond, &pnTZFlag);
    2037             : 
    2038             :                     papstBindBuffer[iCol]->VarChar.nSize = SQL_VARLEN_DATA;
    2039             :                     snprintf((char *)papstBindBuffer[iCol]->VarChar.pData, 8000,
    2040             :                              "%4d-%02d-%02d %02d:%02d:%06.3f", pnYear, pnMonth,
    2041             :                              pnDay, pnHour, pnMinute, pfSecond);
    2042             :                     if (Failed2(bcp_collen(hDBCBCP, SQL_VARLEN_DATA, iCol + 1)))
    2043             :                         return OGRERR_FAILURE;
    2044             :                 }
    2045             :             }
    2046             :             else if (poFDefn->GetType() == OFTDateTime)
    2047             :             {
    2048             :                 /* datetime */
    2049             :                 if (!poFeature->IsFieldSetAndNotNull(iField))
    2050             :                 {
    2051             :                     papstBindBuffer[iCol]->VarChar.nSize = SQL_NULL_DATA;
    2052             :                     /* set NULL */
    2053             :                     if (Failed2(bcp_collen(hDBCBCP, SQL_NULL_DATA, iCol + 1)))
    2054             :                         return OGRERR_FAILURE;
    2055             :                 }
    2056             :                 else
    2057             :                 {
    2058             :                     int pnYear;
    2059             :                     int pnMonth;
    2060             :                     int pnDay;
    2061             :                     int pnHour;
    2062             :                     int pnMinute;
    2063             :                     float pfSecond;
    2064             :                     int pnTZFlag;
    2065             : 
    2066             :                     poFeature->GetFieldAsDateTime(iField, &pnYear, &pnMonth,
    2067             :                                                   &pnDay, &pnHour, &pnMinute,
    2068             :                                                   &pfSecond, &pnTZFlag);
    2069             : 
    2070             :                     papstBindBuffer[iCol]->VarChar.nSize = SQL_VARLEN_DATA;
    2071             :                     snprintf((char *)papstBindBuffer[iCol]->VarChar.pData, 8000,
    2072             :                              "%4d-%02d-%02d %02d:%02d:%06.3f", pnYear, pnMonth,
    2073             :                              pnDay, pnHour, pnMinute, pfSecond);
    2074             : 
    2075             :                     if (Failed2(bcp_collen(hDBCBCP, SQL_VARLEN_DATA, iCol + 1)))
    2076             :                         return OGRERR_FAILURE;
    2077             :                 }
    2078             :             }
    2079             :             else if (poFDefn->GetType() == OFTBinary)
    2080             :             {
    2081             :                 if (!poFeature->IsFieldSetAndNotNull(iField))
    2082             :                 {
    2083             :                     papstBindBuffer[iCol]->RawData.nSize = SQL_NULL_DATA;
    2084             :                     /* set NULL */
    2085             :                     if (Failed2(bcp_collen(hDBCBCP, SQL_NULL_DATA, iCol + 1)))
    2086             :                         return OGRERR_FAILURE;
    2087             :                 }
    2088             :                 else
    2089             :                 {
    2090             :                     /* image */
    2091             :                     int nLen;
    2092             :                     papstBindBuffer[iCol]->RawData.pData =
    2093             :                         poFeature->GetFieldAsBinary(iField, &nLen);
    2094             :                     papstBindBuffer[iCol]->RawData.nSize = nLen;
    2095             : 
    2096             :                     /* set data length */
    2097             :                     if (Failed2(bcp_collen(
    2098             :                             hDBCBCP,
    2099             :                             (DBINT)papstBindBuffer[iCol]->RawData.nSize,
    2100             :                             iCol + 1)))
    2101             :                         return OGRERR_FAILURE;
    2102             :                 }
    2103             :             }
    2104             :             else
    2105             :             {
    2106             :                 CPLError(
    2107             :                     CE_Failure, CPLE_NotSupported,
    2108             :                     "Filed %s with type %s is not supported for bulk insert.",
    2109             :                     poFDefn->GetNameRef(),
    2110             :                     OGRFieldDefn::GetFieldTypeName(poFDefn->GetType()));
    2111             : 
    2112             :                 return OGRERR_FAILURE;
    2113             :             }
    2114             : 
    2115             :             ++iField;
    2116             :         }
    2117             :     }
    2118             : 
    2119             :     /* send row */
    2120             :     if (Failed2(bcp_sendrow(hDBCBCP)))
    2121             :         return OGRERR_FAILURE;
    2122             : 
    2123             :     /* send dynamic data */
    2124             :     iField = 0;
    2125             :     for (iCol = 0; iCol < nRawColumns; iCol++)
    2126             :     {
    2127             :         if (iCol == nGeomColumnIndex)
    2128             :         {
    2129             :             if (papstBindBuffer[iCol]->RawData.nSize != SQL_NULL_DATA)
    2130             :             {
    2131             :                 if (Failed2(bcp_moretext(
    2132             :                         hDBCBCP, (DBINT)papstBindBuffer[iCol]->RawData.nSize,
    2133             :                         papstBindBuffer[iCol]->RawData.pData)))
    2134             :                 {
    2135             :                 }
    2136             :                 CPLFree(papstBindBuffer[iCol]->RawData.pData);
    2137             :                 if (Failed2(bcp_moretext(hDBCBCP, 0, nullptr)))
    2138             :                 {
    2139             :                 }
    2140             :             }
    2141             :             else
    2142             :             {
    2143             :                 if (Failed2(bcp_moretext(hDBCBCP, SQL_NULL_DATA, nullptr)))
    2144             :                 {
    2145             :                 }
    2146             :             }
    2147             :         }
    2148             :         else if (iCol == nFIDColumnIndex)
    2149             :         {
    2150             :             /* TODO */
    2151             :             continue;
    2152             :         }
    2153             :         else if (iField < poFeatureDefn->GetFieldCount() &&
    2154             :                  iCol == panFieldOrdinals[iField])
    2155             :         {
    2156             :             OGRFieldDefn *poFDefn = poFeatureDefn->GetFieldDefn(iField);
    2157             : 
    2158             :             if (poFDefn->GetType() == OFTString)
    2159             :             {
    2160             :                 if (poFDefn->GetWidth() == 0)
    2161             :                 {
    2162             :                     if (poFeature->IsFieldSetAndNotNull(iField))
    2163             :                     {
    2164             :                         const char *pszStr =
    2165             :                             poFeature->GetFieldAsString(iField);
    2166             :                         if (pszStr[0] != 0)
    2167             :                         {
    2168             :                             wchar_t *buffer = CPLRecodeToWChar(
    2169             :                                 poFeature->GetFieldAsString(iField),
    2170             :                                 CPL_ENC_UTF8, CPL_ENC_UCS2);
    2171             :                             const auto nLen = wcslen(buffer);
    2172             :                             papstBindBuffer[iCol]->VarChar.nSize =
    2173             :                                 (SQLLEN)nLen * sizeof(GUInt16);
    2174             : #if WCHAR_MAX > 0xFFFFu
    2175             :                             // Shorten each character to a two-byte value, as
    2176             :                             // expected by the ODBC driver
    2177             :                             GUInt16 *panBuffer =
    2178             :                                 reinterpret_cast<GUInt16 *>(buffer);
    2179             :                             for (unsigned int nIndex = 1; nIndex <= nLen;
    2180             :                                  nIndex += 1)
    2181             :                                 panBuffer[nIndex] =
    2182             :                                     static_cast<GUInt16>(buffer[nIndex]);
    2183             : #endif
    2184             :                             if (Failed2(bcp_moretext(
    2185             :                                     hDBCBCP,
    2186             :                                     (DBINT)papstBindBuffer[iCol]->VarChar.nSize,
    2187             :                                     (LPCBYTE)buffer)))
    2188             :                             {
    2189             :                             }
    2190             : 
    2191             :                             CPLFree(buffer);
    2192             :                         }
    2193             : 
    2194             :                         if (Failed2(bcp_moretext(hDBCBCP, 0, nullptr)))
    2195             :                         {
    2196             :                         }
    2197             :                     }
    2198             :                     else
    2199             :                     {
    2200             :                         if (Failed2(
    2201             :                                 bcp_moretext(hDBCBCP, SQL_NULL_DATA, nullptr)))
    2202             :                         {
    2203             :                         }
    2204             :                     }
    2205             :                 }
    2206             :             }
    2207             :             else if (poFDefn->GetType() == OFTBinary)
    2208             :             {
    2209             :                 if (papstBindBuffer[iCol]->RawData.nSize != SQL_NULL_DATA)
    2210             :                 {
    2211             :                     if (papstBindBuffer[iCol]->RawData.nSize > 0)
    2212             :                     {
    2213             :                         if (Failed2(bcp_moretext(
    2214             :                                 hDBCBCP,
    2215             :                                 (DBINT)papstBindBuffer[iCol]->RawData.nSize,
    2216             :                                 papstBindBuffer[iCol]->RawData.pData)))
    2217             :                         {
    2218             :                         }
    2219             :                     }
    2220             :                     else
    2221             :                     {
    2222             :                         Failed2(bcp_moretext(hDBCBCP, 0, nullptr));
    2223             :                     }
    2224             :                 }
    2225             :                 else
    2226             :                 {
    2227             :                     if (Failed2(bcp_moretext(hDBCBCP, SQL_NULL_DATA, nullptr)))
    2228             :                     {
    2229             :                     }
    2230             :                 }
    2231             :             }
    2232             :             ++iField;
    2233             :         }
    2234             :     }
    2235             : 
    2236             :     if (++nBCPCount >= nBCPSize)
    2237             :     {
    2238             :         /* commit */
    2239             :         int nRecNum = bcp_batch(hDBCBCP);
    2240             :         if (nRecNum == -1)
    2241             :             Failed2(nRecNum);
    2242             : 
    2243             :         nBCPCount = 0;
    2244             :     }
    2245             : 
    2246             :     return OGRERR_NONE;
    2247             : }
    2248             : #endif /* MSSQL_BCP_SUPPORTED */
    2249             : 
    2250             : /************************************************************************/
    2251             : /*                           ICreateFeature()                            */
    2252             : /************************************************************************/
    2253             : 
    2254           0 : OGRErr OGRMSSQLSpatialTableLayer::ICreateFeature(OGRFeature *poFeature)
    2255             : 
    2256             : {
    2257           0 :     if (!bUpdateAccess)
    2258             :     {
    2259           0 :         CPLError(CE_Failure, CPLE_NotSupported, UNSUPPORTED_OP_READ_ONLY,
    2260             :                  "CreateFeature");
    2261           0 :         return OGRERR_FAILURE;
    2262             :     }
    2263             : 
    2264           0 :     GetLayerDefn();
    2265             : 
    2266           0 :     if (nullptr == poFeature)
    2267             :     {
    2268           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2269             :                  "NULL pointer to OGRFeature passed to CreateFeature().");
    2270           0 :         return OGRERR_FAILURE;
    2271             :     }
    2272             : 
    2273             : #if (ODBCVER >= 0x0300) && defined(MSSQL_BCP_SUPPORTED)
    2274             :     if (bUseCopy && !m_bHasUUIDColumn)
    2275             :     {
    2276             :         return CreateFeatureBCP(poFeature);
    2277             :     }
    2278             : #endif
    2279             : 
    2280           0 :     ClearStatement();
    2281             : 
    2282           0 :     CPLODBCSession *poSession = poDS->GetSession();
    2283             : 
    2284             :     /* the fid values are retrieved from the source layer */
    2285           0 :     CPLODBCStatement oStatement(poSession);
    2286             : 
    2287           0 :     if (poFeature->GetFID() != OGRNullFID && pszFIDColumn != nullptr &&
    2288           0 :         bIsIdentityFid)
    2289           0 :         oStatement.Appendf("SET IDENTITY_INSERT [%s].[%s] ON;", pszSchemaName,
    2290             :                            pszTableName);
    2291             : 
    2292             :     /* -------------------------------------------------------------------- */
    2293             :     /*      Form the INSERT command.                                        */
    2294             :     /* -------------------------------------------------------------------- */
    2295             : 
    2296           0 :     oStatement.Appendf("INSERT INTO [%s].[%s] ", pszSchemaName, pszTableName);
    2297             : 
    2298           0 :     OGRGeometry *poGeom = poFeature->GetGeometryRef();
    2299           0 :     GIntBig nFID = poFeature->GetFID();
    2300           0 :     if (bUseGeometryValidation && poGeom != nullptr)
    2301             :     {
    2302           0 :         OGRMSSQLGeometryValidator oValidator(poGeom, nGeomColumnType);
    2303           0 :         if (!oValidator.IsValid())
    2304             :         {
    2305           0 :             oValidator.MakeValid(poGeom);
    2306           0 :             CPLError(CE_Warning, CPLE_NotSupported,
    2307             :                      "Geometry with FID = " CPL_FRMT_GIB
    2308             :                      " has been modified to valid geometry.",
    2309             :                      poFeature->GetFID());
    2310             :         }
    2311             :     }
    2312             : 
    2313           0 :     int bNeedComma = FALSE;
    2314             : 
    2315           0 :     if (poGeom != nullptr && pszGeomColumn != nullptr)
    2316             :     {
    2317           0 :         oStatement.Append("([");
    2318           0 :         oStatement.Append(pszGeomColumn);
    2319           0 :         oStatement.Append("]");
    2320           0 :         bNeedComma = TRUE;
    2321             :     }
    2322             : 
    2323           0 :     if (nFID != OGRNullFID && pszFIDColumn != nullptr)
    2324             :     {
    2325           0 :         if (!CPL_INT64_FITS_ON_INT32(nFID) &&
    2326           0 :             GetMetadataItem(OLMD_FID64) == nullptr)
    2327             :         {
    2328             :             /* MSSQL server doesn't support modifying pk columns without
    2329             :              * recreating the field */
    2330           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2331             :                      "Failed to create feature with large integer fid. "
    2332             :                      "The FID64 layer creation option should be used.");
    2333             : 
    2334           0 :             return OGRERR_FAILURE;
    2335             :         }
    2336             : 
    2337           0 :         if (bNeedComma)
    2338           0 :             oStatement.Appendf(", [%s]", pszFIDColumn);
    2339             :         else
    2340             :         {
    2341           0 :             oStatement.Appendf("([%s]", pszFIDColumn);
    2342           0 :             bNeedComma = TRUE;
    2343             :         }
    2344             :     }
    2345             : 
    2346           0 :     int nFieldCount = poFeatureDefn->GetFieldCount();
    2347             : 
    2348           0 :     int bind_num = 0;
    2349           0 :     void **bind_buffer = (void **)CPLMalloc(sizeof(void *) * (nFieldCount + 1));
    2350             : #ifdef SQL_SS_UDT
    2351             :     SQLLEN *bind_datalen =
    2352             :         (SQLLEN *)CPLMalloc(sizeof(SQLLEN) * (nFieldCount + 1));
    2353             : #endif
    2354             : 
    2355             :     int i;
    2356           0 :     for (i = 0; i < nFieldCount; i++)
    2357             :     {
    2358           0 :         if (!poFeature->IsFieldSetAndNotNull(i))
    2359           0 :             continue;
    2360             : 
    2361           0 :         if (bNeedComma)
    2362           0 :             oStatement.Appendf(", [%s]",
    2363           0 :                                poFeatureDefn->GetFieldDefn(i)->GetNameRef());
    2364             :         else
    2365             :         {
    2366           0 :             oStatement.Appendf("([%s]",
    2367           0 :                                poFeatureDefn->GetFieldDefn(i)->GetNameRef());
    2368           0 :             bNeedComma = TRUE;
    2369             :         }
    2370             :     }
    2371             : 
    2372             :     SQLLEN nWKBLenBindParameter;
    2373           0 :     if (oStatement.GetCommand()[strlen(oStatement.GetCommand()) - 1] != ']')
    2374             :     {
    2375             :         /* no fields were added */
    2376             : 
    2377           0 :         if (nFID == OGRNullFID && pszFIDColumn != nullptr &&
    2378           0 :             (bIsIdentityFid || poDS->AlwaysOutputFid()))
    2379           0 :             oStatement.Appendf(" OUTPUT INSERTED.[%s] DEFAULT VALUES;",
    2380           0 :                                GetFIDColumn());
    2381             :         else
    2382           0 :             oStatement.Appendf("DEFAULT VALUES;");
    2383             :     }
    2384             :     else
    2385             :     {
    2386             :         /* prepend VALUES section */
    2387           0 :         if (nFID == OGRNullFID && pszFIDColumn != nullptr &&
    2388           0 :             (bIsIdentityFid || poDS->AlwaysOutputFid()))
    2389           0 :             oStatement.Appendf(") OUTPUT INSERTED.[%s] VALUES (",
    2390           0 :                                GetFIDColumn());
    2391             :         else
    2392           0 :             oStatement.Appendf(") VALUES (");
    2393             : 
    2394             :         /* Set the geometry */
    2395           0 :         bNeedComma = FALSE;
    2396           0 :         if (poGeom != nullptr && pszGeomColumn != nullptr)
    2397             :         {
    2398           0 :             int nOutgoingSRSId = 0;
    2399             : 
    2400             :             // Use the SRID specified by the provided feature's geometry, if
    2401             :             // its spatial-reference system is known; otherwise, use the SRID
    2402             :             // associated with the table
    2403             :             const OGRSpatialReference *poFeatureSRS =
    2404           0 :                 poGeom->getSpatialReference();
    2405           0 :             if (poFeatureSRS)
    2406           0 :                 nOutgoingSRSId = poDS->FetchSRSId(poFeatureSRS);
    2407           0 :             if (nOutgoingSRSId <= 0)
    2408           0 :                 nOutgoingSRSId = nSRSId;
    2409             : 
    2410           0 :             if (nUploadGeometryFormat == MSSQLGEOMETRY_NATIVE)
    2411             :             {
    2412             : #ifdef SQL_SS_UDT
    2413             :                 OGRMSSQLGeometryWriter poWriter(poGeom, nGeomColumnType,
    2414             :                                                 nOutgoingSRSId);
    2415             :                 bind_datalen[bind_num] = poWriter.GetDataLen();
    2416             :                 GByte *pabyData =
    2417             :                     (GByte *)CPLMalloc(bind_datalen[bind_num] + 1);
    2418             :                 if (poWriter.WriteSqlGeometry(
    2419             :                         pabyData, (int)bind_datalen[bind_num]) == OGRERR_NONE)
    2420             :                 {
    2421             :                     SQLHANDLE ipd;
    2422             :                     if ((!poSession->Failed(SQLBindParameter(
    2423             :                             oStatement.GetStatement(),
    2424             :                             (SQLUSMALLINT)(bind_num + 1), SQL_PARAM_INPUT,
    2425             :                             SQL_C_BINARY, SQL_SS_UDT, SQL_SS_LENGTH_UNLIMITED,
    2426             :                             0, (SQLPOINTER)pabyData, bind_datalen[bind_num],
    2427             :                             (SQLLEN *)&bind_datalen[bind_num]))) &&
    2428             :                         (!poSession->Failed(SQLGetStmtAttr(
    2429             :                             oStatement.GetStatement(), SQL_ATTR_IMP_PARAM_DESC,
    2430             :                             &ipd, 0, nullptr))) &&
    2431             :                         (!poSession->Failed(SQLSetDescField(
    2432             :                             ipd, 1, SQL_CA_SS_UDT_TYPE_NAME,
    2433             :                             const_cast<char *>(nGeomColumnType ==
    2434             :                                                        MSSQLCOLTYPE_GEOGRAPHY
    2435             :                                                    ? "geography"
    2436             :                                                    : "geometry"),
    2437             :                             SQL_NTS))))
    2438             :                     {
    2439             :                         oStatement.Append("?");
    2440             :                         bind_buffer[bind_num] = pabyData;
    2441             :                         ++bind_num;
    2442             :                     }
    2443             :                     else
    2444             :                     {
    2445             :                         oStatement.Append("null");
    2446             :                         CPLFree(pabyData);
    2447             :                     }
    2448             :                 }
    2449             :                 else
    2450             :                 {
    2451             :                     oStatement.Append("null");
    2452             :                     CPLFree(pabyData);
    2453             :                 }
    2454             : #else
    2455           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2456             :                          "Native geometry upload is not supported");
    2457             : 
    2458             :                 // No need to free bind_buffer[i] since bind_num == 0 in that
    2459             :                 // branch
    2460           0 :                 CPLFree(bind_buffer);
    2461             : 
    2462           0 :                 return OGRERR_FAILURE;
    2463             : #endif
    2464             :                 // CPLFree(pabyData);
    2465             :             }
    2466           0 :             else if (nUploadGeometryFormat == MSSQLGEOMETRY_WKB)
    2467             :             {
    2468           0 :                 const size_t nWKBLen = poGeom->WkbSize();
    2469           0 :                 GByte *pabyWKB = (GByte *)VSI_MALLOC_VERBOSE(
    2470             :                     nWKBLen + 1);  // do we need the +1 ?
    2471           0 :                 if (pabyWKB == nullptr)
    2472             :                 {
    2473           0 :                     oStatement.Append("null");
    2474             :                 }
    2475           0 :                 else if (poGeom->exportToWkb(wkbNDR, pabyWKB, wkbVariantIso) ==
    2476           0 :                              OGRERR_NONE &&
    2477           0 :                          (nGeomColumnType == MSSQLCOLTYPE_GEOMETRY ||
    2478           0 :                           nGeomColumnType == MSSQLCOLTYPE_GEOGRAPHY))
    2479             :                 {
    2480           0 :                     nWKBLenBindParameter = nWKBLen;
    2481           0 :                     int nRetCode = SQLBindParameter(
    2482           0 :                         oStatement.GetStatement(), (SQLUSMALLINT)(bind_num + 1),
    2483             :                         SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY,
    2484             :                         nWKBLen, 0, (SQLPOINTER)pabyWKB, nWKBLen,
    2485           0 :                         &nWKBLenBindParameter);
    2486           0 :                     if (nRetCode == SQL_SUCCESS ||
    2487             :                         nRetCode == SQL_SUCCESS_WITH_INFO)
    2488             :                     {
    2489           0 :                         if (nGeomColumnType == MSSQLCOLTYPE_GEOGRAPHY)
    2490             :                         {
    2491           0 :                             oStatement.Append("geography::STGeomFromWKB(?");
    2492           0 :                             oStatement.Appendf(",%d)", nOutgoingSRSId);
    2493             :                         }
    2494             :                         else
    2495             :                         {
    2496           0 :                             oStatement.Append("geometry::STGeomFromWKB(?");
    2497           0 :                             oStatement.Appendf(",%d).MakeValid()",
    2498             :                                                nOutgoingSRSId);
    2499             :                         }
    2500           0 :                         bind_buffer[bind_num] = pabyWKB;
    2501           0 :                         ++bind_num;
    2502             :                     }
    2503             :                     else
    2504             :                     {
    2505           0 :                         oStatement.Append("null");
    2506           0 :                         CPLFree(pabyWKB);
    2507             :                     }
    2508             :                 }
    2509             :                 else
    2510             :                 {
    2511           0 :                     oStatement.Append("null");
    2512           0 :                     CPLFree(pabyWKB);
    2513             :                 }
    2514             :             }
    2515           0 :             else if (nUploadGeometryFormat == MSSQLGEOMETRY_WKT)
    2516             :             {
    2517           0 :                 char *pszWKT = nullptr;
    2518           0 :                 if (poGeom->exportToWkt(&pszWKT) == OGRERR_NONE &&
    2519           0 :                     (nGeomColumnType == MSSQLCOLTYPE_GEOMETRY ||
    2520           0 :                      nGeomColumnType == MSSQLCOLTYPE_GEOGRAPHY))
    2521             :                 {
    2522           0 :                     size_t nLen = 0;
    2523           0 :                     while (pszWKT[nLen] != '\0')
    2524           0 :                         nLen++;
    2525             : 
    2526           0 :                     int nRetCode = SQLBindParameter(
    2527           0 :                         oStatement.GetStatement(), (SQLUSMALLINT)(bind_num + 1),
    2528             :                         SQL_PARAM_INPUT, SQL_C_CHAR, SQL_LONGVARCHAR, nLen, 0,
    2529           0 :                         (SQLPOINTER)pszWKT, 0, nullptr);
    2530           0 :                     if (nRetCode == SQL_SUCCESS ||
    2531             :                         nRetCode == SQL_SUCCESS_WITH_INFO)
    2532             :                     {
    2533           0 :                         if (nGeomColumnType == MSSQLCOLTYPE_GEOGRAPHY)
    2534             :                         {
    2535           0 :                             oStatement.Append("geography::STGeomFromText(?");
    2536           0 :                             oStatement.Appendf(",%d)", nOutgoingSRSId);
    2537             :                         }
    2538             :                         else
    2539             :                         {
    2540           0 :                             oStatement.Append("geometry::STGeomFromText(?");
    2541           0 :                             oStatement.Appendf(",%d).MakeValid()",
    2542             :                                                nOutgoingSRSId);
    2543             :                         }
    2544           0 :                         bind_buffer[bind_num] = pszWKT;
    2545           0 :                         ++bind_num;
    2546             :                     }
    2547             :                     else
    2548             :                     {
    2549           0 :                         oStatement.Append("null");
    2550           0 :                         CPLFree(pszWKT);
    2551             :                     }
    2552             :                 }
    2553             :                 else
    2554             :                 {
    2555           0 :                     oStatement.Append("null");
    2556           0 :                     CPLFree(pszWKT);
    2557             :                 }
    2558             :             }
    2559             :             else
    2560           0 :                 oStatement.Append("null");
    2561             : 
    2562           0 :             bNeedComma = TRUE;
    2563             :         }
    2564             : 
    2565             :         /* Set the FID */
    2566           0 :         if (nFID != OGRNullFID && pszFIDColumn != nullptr)
    2567             :         {
    2568           0 :             if (bNeedComma)
    2569           0 :                 oStatement.Appendf(", " CPL_FRMT_GIB, nFID);
    2570             :             else
    2571             :             {
    2572           0 :                 oStatement.Appendf(CPL_FRMT_GIB, nFID);
    2573           0 :                 bNeedComma = TRUE;
    2574             :             }
    2575             :         }
    2576             : 
    2577           0 :         for (i = 0; i < nFieldCount; i++)
    2578             :         {
    2579           0 :             if (!poFeature->IsFieldSetAndNotNull(i))
    2580           0 :                 continue;
    2581             : 
    2582           0 :             if (bNeedComma)
    2583           0 :                 oStatement.Append(", ");
    2584             :             else
    2585           0 :                 bNeedComma = TRUE;
    2586             : 
    2587           0 :             AppendFieldValue(&oStatement, poFeature, i, &bind_num, bind_buffer);
    2588             :         }
    2589             : 
    2590           0 :         oStatement.Append(");");
    2591             :     }
    2592             : 
    2593           0 :     if (nFID != OGRNullFID && pszFIDColumn != nullptr && bIsIdentityFid)
    2594           0 :         oStatement.Appendf("SET IDENTITY_INSERT [%s].[%s] OFF;", pszSchemaName,
    2595             :                            pszTableName);
    2596             : 
    2597             :     /* -------------------------------------------------------------------- */
    2598             :     /*      Execute the insert.                                             */
    2599             :     /* -------------------------------------------------------------------- */
    2600             : 
    2601           0 :     if (!oStatement.ExecuteSQL())
    2602             :     {
    2603           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2604             :                  "INSERT command for new feature failed. %s",
    2605           0 :                  poDS->GetSession()->GetLastError());
    2606             : 
    2607           0 :         for (i = 0; i < bind_num; i++)
    2608           0 :             CPLFree(bind_buffer[i]);
    2609           0 :         CPLFree(bind_buffer);
    2610             : 
    2611             : #ifdef SQL_SS_UDT
    2612             :         CPLFree(bind_datalen);
    2613             : #endif
    2614             : 
    2615           0 :         return OGRERR_FAILURE;
    2616             :     }
    2617           0 :     else if (nFID == OGRNullFID && pszFIDColumn != nullptr &&
    2618           0 :              (bIsIdentityFid || poDS->AlwaysOutputFid()))
    2619             :     {
    2620             :         // fetch new ID and set it into the feature
    2621           0 :         if (oStatement.Fetch())
    2622             :         {
    2623           0 :             GIntBig newID = atoll(oStatement.GetColData(0));
    2624           0 :             poFeature->SetFID(newID);
    2625             :         }
    2626             :     }
    2627             : 
    2628           0 :     for (i = 0; i < bind_num; i++)
    2629           0 :         CPLFree(bind_buffer[i]);
    2630           0 :     CPLFree(bind_buffer);
    2631             : 
    2632             : #ifdef SQL_SS_UDT
    2633             :     CPLFree(bind_datalen);
    2634             : #endif
    2635             : 
    2636           0 :     return OGRERR_NONE;
    2637             : }
    2638             : 
    2639             : /************************************************************************/
    2640             : /*                          AppendFieldValue()                          */
    2641             : /*                                                                      */
    2642             : /* Used by CreateFeature() and SetFeature() to format a                 */
    2643             : /* non-empty field value                                                */
    2644             : /************************************************************************/
    2645             : 
    2646           0 : void OGRMSSQLSpatialTableLayer::AppendFieldValue(CPLODBCStatement *poStatement,
    2647             :                                                  OGRFeature *poFeature, int i,
    2648             :                                                  int *bind_num,
    2649             :                                                  void **bind_buffer)
    2650             : {
    2651           0 :     int nOGRFieldType = poFeatureDefn->GetFieldDefn(i)->GetType();
    2652           0 :     int nOGRFieldSubType = poFeatureDefn->GetFieldDefn(i)->GetSubType();
    2653             : 
    2654             :     // We need special formatting for integer list values.
    2655           0 :     if (nOGRFieldType == OFTIntegerList)
    2656             :     {
    2657             :         // TODO
    2658           0 :         poStatement->Append("null");
    2659           0 :         return;
    2660             :     }
    2661             : 
    2662             :     // We need special formatting for real list values.
    2663           0 :     else if (nOGRFieldType == OFTRealList)
    2664             :     {
    2665             :         // TODO
    2666           0 :         poStatement->Append("null");
    2667           0 :         return;
    2668             :     }
    2669             : 
    2670             :     // We need special formatting for string list values.
    2671           0 :     else if (nOGRFieldType == OFTStringList)
    2672             :     {
    2673             :         // TODO
    2674           0 :         poStatement->Append("null");
    2675           0 :         return;
    2676             :     }
    2677             : 
    2678             :     // Binary formatting
    2679           0 :     if (nOGRFieldType == OFTBinary)
    2680             :     {
    2681           0 :         int nLen = 0;
    2682           0 :         GByte *pabyData = poFeature->GetFieldAsBinary(i, &nLen);
    2683           0 :         char *pszBytes = GByteArrayToHexString(pabyData, nLen);
    2684           0 :         poStatement->Append(pszBytes);
    2685           0 :         CPLFree(pszBytes);
    2686           0 :         return;
    2687             :     }
    2688             : 
    2689             :     // Datetime values need special handling as SQL Server's datetime type
    2690             :     // accepts values only in ISO 8601 format and only without time zone
    2691             :     // information
    2692           0 :     else if (nOGRFieldType == OFTDateTime)
    2693             :     {
    2694           0 :         char *pszStrValue = OGRGetXMLDateTime((*poFeature)[i].GetRawValue());
    2695             : 
    2696           0 :         int nRetCode = SQLBindParameter(
    2697           0 :             poStatement->GetStatement(), (SQLUSMALLINT)((*bind_num) + 1),
    2698           0 :             SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, strlen(pszStrValue) + 1,
    2699           0 :             0, (SQLPOINTER)pszStrValue, 0, nullptr);
    2700           0 :         if (nRetCode == SQL_SUCCESS || nRetCode == SQL_SUCCESS_WITH_INFO)
    2701             :         {
    2702           0 :             bind_buffer[*bind_num] = pszStrValue;
    2703           0 :             ++(*bind_num);
    2704           0 :             poStatement->Append("CAST(CAST(? AS datetimeoffset) AS datetime)");
    2705             :         }
    2706             :         else
    2707             :         {
    2708           0 :             poStatement->Append(CPLSPrintf(
    2709             :                 "CAST(CAST('%s' AS datetimeoffset) AS datetime)", pszStrValue));
    2710           0 :             CPLFree(pszStrValue);
    2711             :         }
    2712           0 :         return;
    2713             :     }
    2714             : 
    2715             :     // Flag indicating NULL or not-a-date date value
    2716             :     // e.g. 0000-00-00 - there is no year 0
    2717           0 :     OGRBoolean bIsDateNull = FALSE;
    2718             : 
    2719           0 :     const char *pszStrValue = poFeature->GetFieldAsString(i);
    2720             : 
    2721             :     // Check if date is NULL: 0000-00-00
    2722           0 :     if (nOGRFieldType == OFTDate)
    2723             :     {
    2724           0 :         if (STARTS_WITH_CI(pszStrValue, "0000"))
    2725             :         {
    2726           0 :             pszStrValue = "null";
    2727           0 :             bIsDateNull = TRUE;
    2728             :         }
    2729             :     }
    2730           0 :     else if (nOGRFieldType == OFTReal)
    2731             :     {
    2732           0 :         char *pszComma = strchr((char *)pszStrValue, ',');
    2733           0 :         if (pszComma)
    2734           0 :             *pszComma = '.';
    2735             :     }
    2736             : 
    2737           0 :     if (nOGRFieldType != OFTInteger && nOGRFieldType != OFTInteger64 &&
    2738           0 :         nOGRFieldType != OFTReal && !bIsDateNull)
    2739             :     {
    2740           0 :         if (nOGRFieldType == OFTString)
    2741             :         {
    2742           0 :             if (nOGRFieldSubType == OFSTUUID)
    2743             :             {
    2744             :                 int nRetCode =
    2745           0 :                     SQLBindParameter(poStatement->GetStatement(),
    2746           0 :                                      (SQLUSMALLINT)((*bind_num) + 1),
    2747             :                                      SQL_PARAM_INPUT, SQL_C_CHAR, SQL_GUID, 16,
    2748           0 :                                      0, (SQLPOINTER)pszStrValue, 0, nullptr);
    2749           0 :                 if (nRetCode == SQL_SUCCESS ||
    2750             :                     nRetCode == SQL_SUCCESS_WITH_INFO)
    2751             :                 {
    2752           0 :                     poStatement->Append("?");
    2753           0 :                     bind_buffer[*bind_num] = CPLStrdup(pszStrValue);
    2754           0 :                     ++(*bind_num);
    2755             :                 }
    2756             :                 else
    2757             :                 {
    2758           0 :                     OGRMSSQLAppendEscaped(poStatement, pszStrValue);
    2759             :                 }
    2760             :             }
    2761             :             else
    2762             :             {
    2763             :                 // bind UTF8 as unicode parameter
    2764             :                 wchar_t *buffer =
    2765           0 :                     CPLRecodeToWChar(pszStrValue, CPL_ENC_UTF8, CPL_ENC_UCS2);
    2766           0 :                 size_t nLen = wcslen(buffer) + 1;
    2767           0 :                 if (nLen > 4000)
    2768             :                 {
    2769             :                     /* need to handle nvarchar(max) */
    2770             : #ifdef SQL_SS_LENGTH_UNLIMITED
    2771             :                     nLen = SQL_SS_LENGTH_UNLIMITED;
    2772             : #else
    2773             :                     /* for older drivers truncate the data to 4000 chars */
    2774           0 :                     buffer[4000] = 0;
    2775           0 :                     nLen = 4000;
    2776           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    2777             :                              "String data truncation applied on field: %s. Use "
    2778             :                              "a more recent ODBC driver that supports handling "
    2779             :                              "large string values.",
    2780           0 :                              poFeatureDefn->GetFieldDefn(i)->GetNameRef());
    2781             : #endif
    2782             :                 }
    2783             : #if WCHAR_MAX > 0xFFFFu
    2784             :                 // Shorten each character to a two-byte value, as expected by
    2785             :                 // the ODBC driver
    2786           0 :                 GUInt16 *panBuffer = reinterpret_cast<GUInt16 *>(buffer);
    2787           0 :                 for (unsigned int nIndex = 1; nIndex < nLen; nIndex += 1)
    2788           0 :                     panBuffer[nIndex] = static_cast<GUInt16>(buffer[nIndex]);
    2789             : #endif
    2790             :                 int nRetCode =
    2791           0 :                     SQLBindParameter(poStatement->GetStatement(),
    2792           0 :                                      (SQLUSMALLINT)((*bind_num) + 1),
    2793             :                                      SQL_PARAM_INPUT, SQL_C_WCHAR, SQL_WVARCHAR,
    2794           0 :                                      nLen, 0, (SQLPOINTER)buffer, 0, nullptr);
    2795           0 :                 if (nRetCode == SQL_SUCCESS ||
    2796             :                     nRetCode == SQL_SUCCESS_WITH_INFO)
    2797             :                 {
    2798           0 :                     poStatement->Append("?");
    2799           0 :                     bind_buffer[*bind_num] = buffer;
    2800           0 :                     ++(*bind_num);
    2801             :                 }
    2802             :                 else
    2803             :                 {
    2804           0 :                     OGRMSSQLAppendEscaped(poStatement, pszStrValue);
    2805           0 :                     CPLFree(buffer);
    2806             :                 }
    2807             :             }
    2808             :         }
    2809             :         else
    2810           0 :             OGRMSSQLAppendEscaped(poStatement, pszStrValue);
    2811             :     }
    2812             :     else
    2813             :     {
    2814           0 :         poStatement->Append(pszStrValue);
    2815             :     }
    2816             : }

Generated by: LCOV version 1.14