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

Generated by: LCOV version 1.14