LCOV - code coverage report
Current view: top level - ezlibs - ezSqlite.hpp (source / functions) Coverage Total Hit
Test: Coverage (llvm-cov → lcov → genhtml) Lines: 53.5 % 957 512
Test Date: 2025-09-16 22:55:37 Functions: 57.8 % 45 26
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 53.1 % 704 374

             Branch data     Line data    Source code
       1                 :             : #pragma once
       2                 :             : 
       3                 :             : /*
       4                 :             : MIT License
       5                 :             : 
       6                 :             : Copyright (c) 2014-2024 Stephane Cuillerdier (aka aiekick)
       7                 :             : 
       8                 :             : Permission is hereby granted, free of charge, to any person obtaining a copy
       9                 :             : of this software and associated documentation files (the "Software"), to deal
      10                 :             : in the Software without restriction, including without limitation the rights
      11                 :             : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      12                 :             : copies of the Software, and to permit persons to whom the Software is
      13                 :             : furnished to do so, subject to the following conditions:
      14                 :             : 
      15                 :             : The above copyright notice and this permission notice shall be included in all
      16                 :             : copies or substantial portions of the Software.
      17                 :             : 
      18                 :             : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      19                 :             : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      20                 :             : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      21                 :             : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      22                 :             : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      23                 :             : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      24                 :             : SOFTWARE.
      25                 :             : */
      26                 :             : 
      27                 :             : // ezSqlite is part of the ezLibs project : https://github.com/aiekick/ezLibs.git
      28                 :             : 
      29                 :             : #include <cstdint>
      30                 :             : #include <cstring>
      31                 :             : #include <string>
      32                 :             : #include <vector>
      33                 :             : #include <map>
      34                 :             : #include <sstream>  //stringstream
      35                 :             : #include <cstdarg>  // variadic
      36                 :             : #include <memory>
      37                 :             : #include <limits>
      38                 :             : 
      39                 :             : #include "ezStr.hpp"
      40                 :             : 
      41                 :             : namespace ez {
      42                 :             : namespace sqlite {
      43                 :             : 
      44                 :             : #ifdef SQLITE_API
      45                 :             : inline std::string readStringColumn(sqlite3_stmt* vStmt, int32_t vColumn) {
      46                 :             :     const char* str = reinterpret_cast<const char*>(sqlite3_column_text(vStmt, vColumn));
      47                 :             :     if (str != nullptr) {
      48                 :             :         return str;
      49                 :             :     }
      50                 :             :     return {};
      51                 :             : }
      52                 :             : #endif
      53                 :             : 
      54                 :             : // to test
      55                 :             : // Optionnal Insert Query
      56                 :             : // this query replace INSERT OR IGNORE who cause the auto increment
      57                 :             : // of the AUTOINCREMENT KEY even if not inserted and ignored
      58                 :             : /*
      59                 :             :  * INSERT INTO banks (number, name)
      60                 :             :  * SELECT '30003', 'LCL'
      61                 :             :  * WHERE NOT EXISTS (
      62                 :             :  *     SELECT 1 FROM banks WHERE number = '30002' and name = 'LCL'
      63                 :             :  * );
      64                 :             :  *
      65                 :             :  * give :
      66                 :             :  * QueryBuilder().
      67                 :             :  *  setTable("banks").
      68                 :             :  *  addOrSetField("number", 30002).
      69                 :             :  *  addOrSetField("name", "LCL").
      70                 :             :  *  build(QueryType::INSERT_IF_NOT_EXIST);
      71                 :             :  */
      72                 :             : // ex : build("banks", {{"number", "30002"},{"name","LCL"}});
      73                 :             : 
      74                 :             : enum class QueryType { INSERT = 0, UPDATE, INSERT_IF_NOT_EXIST, Count };
      75                 :             : 
      76                 :             : class QueryBuilder {
      77                 :             : private:
      78                 :             :     class Field {
      79                 :             :     private:
      80                 :             :         std::string m_key;
      81                 :             :         std::string m_value;
      82                 :             :         bool m_subQuery = false;
      83                 :             : 
      84                 :             :     public:
      85                 :             :         Field() = default;
      86                 :           0 :         explicit Field(const std::string& vKey, const std::string& vValue, const bool vSubQuery) : m_key(vKey), m_value(vValue), m_subQuery(vSubQuery) {
      87                 :           0 :             const auto zero_pos = m_value.find('\0');
      88                 :           0 :             if (zero_pos != std::string::npos) {
      89                 :           0 :                 m_value.clear();
      90                 :           0 :             }
      91                 :           0 :         }
      92                 :           0 :         const std::string& getRawKey() const { return m_key; }
      93                 :           0 :         const std::string& getRawValue() const { return m_value; }
      94                 :           0 :         std::string getFinalValue() const {
      95                 :           0 :             if (m_subQuery) {
      96                 :           0 :                 return "(" + m_value + ")";
      97                 :           0 :             }
      98                 :           0 :             return "\"" + m_value + "\"";
      99                 :           0 :         }
     100                 :             :     };
     101                 :             : 
     102                 :             :     std::string m_table;
     103                 :             :     std::map<std::string, Field> m_dicoFields;
     104                 :             :     std::vector<std::string> m_fields;
     105                 :             :     std::vector<std::string> m_where;
     106                 :             : 
     107                 :             : public:
     108                 :           0 :     QueryBuilder& setTable(const std::string& vTable) {
     109                 :           0 :         m_table = vTable;
     110                 :           0 :         return *this;
     111                 :           0 :     }
     112                 :           0 :     QueryBuilder& addOrSetField(const std::string& vKey, const std::string& vValue) {
     113                 :           0 :         m_addKeyIfNotExist(vKey);
     114                 :           0 :         m_dicoFields[vKey] = Field(vKey, vValue, false);
     115                 :           0 :         return *this;
     116                 :           0 :     }
     117                 :           0 :     QueryBuilder& addOrSetField(const std::string& vKey, const char* fmt, ...) {
     118                 :           0 :         va_list args;
     119                 :           0 :         va_start(args, fmt);
     120                 :           0 :         static char TempBuffer[1024 + 1];
     121                 :           0 :         const int w = vsnprintf(TempBuffer, 1024, fmt, args);
     122                 :           0 :         va_end(args);
     123                 :           0 :         if (w > 0) {
     124                 :           0 :             m_addKeyIfNotExist(vKey);
     125                 :           0 :             m_dicoFields[vKey] = Field(vKey, std::string(TempBuffer), false);
     126                 :           0 :         }
     127                 :           0 :         return *this;
     128                 :           0 :     }
     129                 :             :     template <typename T>
     130                 :             :     QueryBuilder& addOrSetField(const std::string& vKey, const T& vValue) {
     131                 :             :         m_addKeyIfNotExist(vKey);
     132                 :             :         m_dicoFields[vKey] = Field(vKey, ez::str::toStr<T>(vValue), false);
     133                 :             :         return *this;
     134                 :             :     }
     135                 :           0 :     QueryBuilder& addOrSetFieldQuery(const std::string& vKey, const std::string& vValue) {
     136                 :           0 :         m_addKeyIfNotExist(vKey);
     137                 :           0 :         m_dicoFields[vKey] = Field(vKey, vValue, true);
     138                 :           0 :         return *this;
     139                 :           0 :     }
     140                 :           0 :     QueryBuilder& addOrSetFieldQuery(const std::string& vKey, const char* fmt, ...) {
     141                 :           0 :         va_list args;
     142                 :           0 :         va_start(args, fmt);
     143                 :           0 :         static char TempBuffer[1024 + 1];
     144                 :           0 :         const int w = vsnprintf(TempBuffer, 1024, fmt, args);
     145                 :           0 :         va_end(args);
     146                 :           0 :         if (w > 0) {
     147                 :           0 :             m_addKeyIfNotExist(vKey);
     148                 :           0 :             m_dicoFields[vKey] = Field(vKey, std::string(TempBuffer), true);
     149                 :           0 :         }
     150                 :           0 :         return *this;
     151                 :           0 :     }
     152                 :             :     template <typename T>
     153                 :             :     QueryBuilder& addWhere(const T& vValue) {
     154                 :             :         m_where.emplace_back(ez::str::toStr<T>(vValue));
     155                 :             :         return *this;
     156                 :             :     }
     157                 :           0 :     QueryBuilder& addWhere(const std::string& vValue) {
     158                 :           0 :         m_where.emplace_back(vValue);
     159                 :           0 :         return *this;
     160                 :           0 :     }
     161                 :           0 :     QueryBuilder& addWhere(const char* fmt, ...) {
     162                 :           0 :         va_list args;
     163                 :           0 :         va_start(args, fmt);
     164                 :           0 :         static char TempBuffer[1024 + 1];
     165                 :           0 :         const int w = vsnprintf(TempBuffer, 1024, fmt, args);
     166                 :           0 :         va_end(args);
     167                 :           0 :         if (w) {
     168                 :           0 :             m_where.emplace_back(std::string(TempBuffer));
     169                 :           0 :         }
     170                 :           0 :         return *this;
     171                 :           0 :     }
     172                 :           0 :     std::string build(const QueryType vType) {
     173                 :           0 :         switch (vType) {
     174                 :           0 :             case QueryType::INSERT: return m_buildTypeInsert();
     175                 :           0 :             case QueryType::UPDATE: return m_buildTypeUpdate();
     176                 :           0 :             case QueryType::INSERT_IF_NOT_EXIST: return m_buildTypeInsertIfNotExist();
     177                 :           0 :             case QueryType::Count:
     178                 :           0 :             default: break;
     179                 :           0 :         }
     180                 :           0 :         return {};
     181                 :           0 :     }
     182                 :             : 
     183                 :             : private:
     184                 :           0 :     void m_addKeyIfNotExist(const std::string& vKey) {
     185                 :           0 :         if (m_dicoFields.find(vKey) == m_dicoFields.end()) {
     186                 :           0 :             m_fields.emplace_back(vKey);
     187                 :           0 :         }
     188                 :           0 :     }
     189                 :           0 :     std::string m_buildTypeInsert() {
     190                 :           0 :         std::string query = "INSERT INTO " + m_table + " (\n\t";
     191                 :           0 :         size_t idx = 0;
     192                 :           0 :         for (const auto& field : m_fields) {
     193                 :           0 :             if (idx != 0) {
     194                 :           0 :                 query += ",\n\t";
     195                 :           0 :             }
     196                 :           0 :             query += m_dicoFields.at(field).getRawKey();
     197                 :           0 :             ++idx;
     198                 :           0 :         }
     199                 :           0 :         query += "\n) VALUES (\n\t";
     200                 :           0 :         idx = 0;
     201                 :           0 :         for (const auto& field : m_fields) {
     202                 :           0 :             if (idx != 0) {
     203                 :           0 :                 query += ",\n\t";
     204                 :           0 :             }
     205                 :           0 :             query += m_dicoFields.at(field).getFinalValue();
     206                 :           0 :             ++idx;
     207                 :           0 :         }
     208                 :           0 :         query += "\n);";
     209                 :           0 :         return query;
     210                 :           0 :     }
     211                 :           0 :     std::string m_buildTypeInsertIfNotExist() {
     212                 :           0 :         std::string query = "INSERT INTO " + m_table + " (\n\t";
     213                 :           0 :         size_t idx = 0;
     214                 :           0 :         for (const auto& field : m_fields) {
     215                 :           0 :             if (idx != 0) {
     216                 :           0 :                 query += ",\n\t";
     217                 :           0 :             }
     218                 :           0 :             query += m_dicoFields.at(field).getRawKey();
     219                 :           0 :             ++idx;
     220                 :           0 :         }
     221                 :           0 :         query += "\n) SELECT \n\t";
     222                 :           0 :         idx = 0;
     223                 :           0 :         for (const auto& field : m_fields) {
     224                 :           0 :             if (idx != 0) {
     225                 :           0 :                 query += ",\n\t";
     226                 :           0 :             }
     227                 :           0 :             query += m_dicoFields.at(field).getFinalValue();
     228                 :           0 :             ++idx;
     229                 :           0 :         }
     230                 :           0 :         query += " WHERE NOT EXISTS (SELECT 1 FROM " + m_table + "\nWHERE\n\t";
     231                 :           0 :         idx = 0;
     232                 :           0 :         for (const auto& field : m_fields) {
     233                 :           0 :             if (idx != 0) {
     234                 :           0 :                 query += "\n\tAND ";
     235                 :           0 :             }
     236                 :           0 :             query += m_dicoFields.at(field).getRawKey() + " = " + m_dicoFields.at(field).getFinalValue();
     237                 :           0 :             ++idx;
     238                 :           0 :         }
     239                 :           0 :         query += "\n);";
     240                 :           0 :         return query;
     241                 :           0 :     }
     242                 :           0 :     std::string m_buildTypeUpdate() {
     243                 :           0 :         std::string query = "UPDATE " + m_table + " SET\n\t";
     244                 :           0 :         size_t idx = 0;
     245                 :           0 :         for (const auto& field : m_fields) {
     246                 :           0 :             if (idx != 0) {
     247                 :           0 :                 query += ",\n\t";
     248                 :           0 :             }
     249                 :           0 :             query += m_dicoFields.at(field).getRawKey() + " = " + m_dicoFields.at(field).getFinalValue();
     250                 :           0 :             ++idx;
     251                 :           0 :         }
     252                 :           0 :         query += "\nWHERE\n\t";
     253                 :           0 :         idx = 0;
     254                 :           0 :         for (const auto& where : m_where) {
     255                 :           0 :             if (idx != 0) {
     256                 :           0 :                 query += "\tAND ";
     257                 :           0 :             }
     258                 :           0 :             query += "(" + where + ")\n";
     259                 :           0 :             ++idx;
     260                 :           0 :         }
     261                 :           0 :         query += ";";
     262                 :           0 :         return query;
     263                 :           0 :     }
     264                 :             : };
     265                 :             : 
     266                 :             : //---------------------------------------------
     267                 :             : // Parser
     268                 :             : //---------------------------------------------
     269                 :             : class Parser {
     270                 :             : public:
     271                 :             :     struct StringRef {
     272                 :             :         const char* data;
     273                 :             :         size_t size;
     274                 :         101 :         StringRef() : data(NULL), size(0u) {}
     275                 :         152 :         StringRef(const char* vData, size_t vSize) : data(vData), size(vSize) {}
     276                 :           0 :         bool empty() const { return size == 0u; }
     277                 :           0 :         std::string toString() const { return size ? std::string(data, size) : std::string(); }
     278                 :             :     };
     279                 :             : 
     280                 :             :     struct SourcePos {
     281                 :             :         uint32_t offset;  // octets depuis le d?but
     282                 :             :         uint32_t line;  // 1-based
     283                 :             :         uint32_t column;  // 1-based (en octets)
     284                 :         215 :         SourcePos() : offset(0u), line(0u), column(0u) {}
     285                 :             :     };
     286                 :             : 
     287                 :             :     struct Error {
     288                 :             :         SourcePos pos;
     289                 :             :         std::string message;  // ex: "token inexpected"
     290                 :             :         std::string expectedHint;  // ex: "expected: FROM | VALUES"
     291                 :          13 :         Error() {}
     292                 :             :     };
     293                 :             : 
     294                 :             :     // clang-format off
     295                 :             :     enum class TokenKind : uint16_t {
     296                 :             :         Identifier, String, Number, Blob,
     297                 :             :         Parameter,                 // ?, ?123, :name, @name, $name
     298                 :             : 
     299                 :             :         // Mots-cl?s (sous-ensemble utile)
     300                 :             :         KwSelect, KwFrom, KwWhere, KwGroup, KwBy, KwHaving,
     301                 :             :         KwOrder, KwLimit, KwOffset, KwWith,
     302                 :             :         KwInsert, KwInto, KwValues, KwUpdate, KwSet, KwDelete,
     303                 :             :         KwCreate, KwTable, KwIf, KwNot, KwExists, KwPrimary, KwKey,
     304                 :             :         KwUnique, KwCheck, KwReferences, KwWithout, KwRowid, KwOn, KwConflict,
     305                 :             :         KwAs,
     306                 :             : 
     307                 :             :         // Op?rateurs / d?limiteurs
     308                 :             :         Plus, Minus, Star, Slash, Percent, PipePipe, Amp, Pipe, Tilde,
     309                 :             :         Shl, Shr, Eq, EqEq, Ne, Ne2, Lt, Le, Gt, Ge, Assign,
     310                 :             :         Comma, Dot, LParen, RParen, Semicolon,
     311                 :             : 
     312                 :             :         EndOfFile,
     313                 :             :         Unknown
     314                 :             :     };
     315                 :             :     // clang-format on
     316                 :             : 
     317                 :             :     struct Token {
     318                 :             :         TokenKind kind{TokenKind::Unknown};
     319                 :             :         SourcePos start;
     320                 :             :         SourcePos end;  // end.offset = 1 + dernier octet inclus (pour affichage)
     321                 :             :         StringRef lex;  // vue sur vSql (pas de copie)
     322                 :             :     };
     323                 :             : 
     324                 :             :     struct StatementRange {
     325                 :             :         uint32_t beginOffset{};  // inclus
     326                 :             :         uint32_t endOffset{};  // exclus
     327                 :             :     };
     328                 :             : 
     329                 :             :     enum class StatementKind : uint8_t { Select, Insert, Update, Delete, CreateTable, Other };
     330                 :             : 
     331                 :             :     struct Statement {
     332                 :             :         StatementKind kind{StatementKind::Other};
     333                 :             :         StatementRange range;
     334                 :             :     };
     335                 :             : 
     336                 :             :     struct Options {
     337                 :             :         bool allowNestedBlockComments = false;  // /* ... /* ... */ ... */ (si true)
     338                 :             :         bool trackAllTokens = true;  // remplir Report.tokens
     339                 :             :         bool caseInsensitiveKeywords = true;  // LIKE SQLite
     340                 :             :     };
     341                 :             : 
     342                 :             :     struct Report {
     343                 :             :         bool ok{true};
     344                 :             :         std::vector<Error> errors;
     345                 :             :         std::vector<Statement> statements;
     346                 :             :         std::vector<Token> tokens;  // rempli si trackAllTokens = true
     347                 :             :     };
     348                 :             : 
     349                 :             : private:
     350                 :             :     // --------- private (static utils)
     351 [ +  + ][ -  + ]:         153 :     static bool m_isSpace(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; }
         [ +  + ][ -  + ]
         [ -  + ][ -  + ]
     352 [ +  + ][ +  + ]:         158 :     static bool m_isDigit(char c) { return c >= '0' && c <= '9'; }
     353 [ #  # ][ #  # ]:           0 :     static bool m_isHex(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); }
         [ #  # ][ #  # ]
         [ #  # ][ #  # ]
     354 [ +  + ][ +  + ]:         290 :     static bool m_isAlpha(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_'); }
         [ +  + ][ +  - ]
                 [ +  + ]
     355 [ +  + ][ -  + ]:         213 :     static bool m_isAlnum(char c) { return m_isAlpha(c) || m_isDigit(c); }
     356 [ +  + ][ +  - ]:         506 :     static char m_up(char c) { return (c >= 'a' && c <= 'z') ? static_cast<char>(c - 32) : c; }
     357                 :         878 :     static bool m_ieq(const StringRef& a, const char* b) {
     358         [ -  + ]:         878 :         if (!b)
     359                 :           0 :             return false;
     360                 :         878 :         size_t blen = 0u;
     361         [ +  + ]:        5240 :         while (b[blen] != '\0')
     362                 :        4362 :             ++blen;
     363         [ +  + ]:         878 :         if (a.size != blen)
     364                 :         761 :             return false;
     365         [ +  + ]:         284 :         for (size_t i = 0; i < a.size; ++i) {
     366         [ +  + ]:         253 :             if (m_up(a.data[i]) != m_up(b[i]))
     367                 :          86 :                 return false;
     368                 :         253 :         }
     369                 :          31 :         return true;
     370                 :         117 :     }
     371                 :             : 
     372                 :             : private:
     373                 :             :     // --------- private (vars)
     374                 :             :     Options m_options;
     375                 :             :     uint32_t m_sourceSize{};
     376                 :             :     std::vector<uint32_t> m_lineStarts;  // offset du d?but de chaque ligne
     377                 :             : 
     378                 :             : public:
     379                 :             :     // --------- public (methods)
     380                 :             :     Parser() = default;
     381                 :          14 :     Parser(const Options& vOptions) : m_options(vOptions) {}
     382                 :             : 
     383                 :             :     // API principale
     384                 :          14 :     bool parse(const std::string& vSql, Report& vOut) {
     385                 :          14 :         m_sourceSize = static_cast<uint32_t>(vSql.size());
     386                 :          14 :         m_buildLineStarts(vSql);
     387                 :             : 
     388                 :          14 :         vOut.ok = true;
     389                 :          14 :         vOut.errors.clear();
     390                 :          14 :         vOut.statements.clear();
     391         [ +  + ]:          14 :         if (m_options.trackAllTokens)
     392                 :          13 :             vOut.tokens.clear();
     393                 :             : 
     394                 :             :         // 1) Lexing
     395                 :          14 :         std::vector<Token> toks;
     396                 :          14 :         std::vector<Error> lexErrs;
     397                 :          14 :         m_lex(vSql, toks, lexErrs);
     398                 :             : 
     399         [ +  + ]:          14 :         if (m_options.trackAllTokens) {
     400                 :          13 :             vOut.tokens = toks;
     401                 :          13 :         }
     402         [ -  + ]:          14 :         if (!lexErrs.empty()) {
     403         [ #  # ]:           0 :             for (size_t i = 0; i < lexErrs.size(); ++i)
     404                 :           0 :                 vOut.errors.push_back(lexErrs[i]);
     405                 :           0 :         }
     406                 :             : 
     407                 :             :         // 2) Split statements
     408                 :          14 :         std::vector<StatementRange> ranges;
     409                 :          14 :         m_splitStatements(toks, ranges);
     410                 :             : 
     411                 :             :         // 3) D?tection kind + v?rifs structurelles
     412         [ +  + ]:          28 :         for (size_t i = 0; i < ranges.size(); ++i) {
     413                 :          14 :             const StatementRange& r = ranges[i];
     414                 :          14 :             Statement st;
     415                 :          14 :             st.range = r;
     416                 :          14 :             st.kind = m_detectKind(toks, r);
     417                 :             :             // V?rifs g?n?rales: parenth?ses
     418                 :          14 :             m_checkParens(toks, r, vOut);
     419                 :             : 
     420                 :             :             // V?rifs par kind
     421                 :          14 :             switch (st.kind) {
     422         [ +  + ]:           2 :                 case StatementKind::CreateTable: m_checkCreateTable(toks, r, vSql, vOut); break;
     423         [ +  + ]:           2 :                 case StatementKind::Insert: m_checkInsert(toks, r, vOut); break;
     424         [ +  + ]:           1 :                 case StatementKind::Update: m_checkUpdate(toks, r, vOut); break;
     425         [ +  + ]:           1 :                 case StatementKind::Delete: m_checkDelete(toks, r, vOut); break;
     426         [ +  + ]:           8 :                 case StatementKind::Select: m_checkSelect(toks, r, vOut); break;
     427         [ -  + ]:           0 :                 default: break;
     428                 :          14 :             }
     429                 :          14 :             vOut.statements.push_back(st);
     430                 :          14 :         }
     431                 :             : 
     432                 :          14 :         vOut.ok = vOut.errors.empty();
     433                 :          14 :         return true;  // true = le parse a tourn? ; vOut.ok dit s'il y a des erreurs
     434                 :          14 :     }
     435                 :             : 
     436                 :         201 :     bool computeLineColumn(uint32_t vOffset, uint32_t& vOutLine, uint32_t& vOutColumn) const {
     437         [ -  + ]:         201 :         if (m_lineStarts.empty())
     438                 :           0 :             return false;
     439         [ -  + ]:         201 :         if (vOffset > m_sourceSize)
     440                 :           0 :             return false;
     441                 :             :         // recherche binaire
     442                 :         201 :         uint32_t lo = 0u;
     443                 :         201 :         uint32_t hi = static_cast<uint32_t>(m_lineStarts.size());
     444         [ +  + ]:         327 :         while (lo + 1u < hi) {
     445                 :         126 :             uint32_t mid = lo + ((hi - lo) >> 1);
     446         [ +  + ]:         126 :             if (m_lineStarts[mid] <= vOffset)
     447                 :          59 :                 lo = mid;
     448                 :          67 :             else
     449                 :          67 :                 hi = mid;
     450                 :         126 :         }
     451                 :         201 :         vOutLine = lo + 1u;  // 1-based
     452                 :         201 :         vOutColumn = vOffset - m_lineStarts[lo] + 1u;
     453                 :         201 :         return true;
     454                 :         201 :     }
     455                 :             : 
     456                 :             : private:
     457                 :             :     // --------- private (methods)
     458                 :          14 :     void m_buildLineStarts(const std::string& vSql) {
     459                 :          14 :         m_lineStarts.clear();
     460                 :          14 :         m_lineStarts.push_back(0u);
     461         [ +  + ]:         329 :         for (uint32_t i = 0; i < static_cast<uint32_t>(vSql.size()); ++i) {
     462                 :         315 :             char c = vSql[i];
     463         [ +  + ]:         315 :             if (c == '\n') {
     464                 :           7 :                 m_lineStarts.push_back(i + 1u);
     465         [ -  + ]:         308 :             } else if (c == '\r') {
     466 [ #  # ][ #  # ]:           0 :                 if (i + 1u < vSql.size() && vSql[i + 1u] == '\n') {
     467                 :           0 :                     m_lineStarts.push_back(i + 2u);
     468                 :           0 :                     ++i;
     469                 :           0 :                 } else {
     470                 :           0 :                     m_lineStarts.push_back(i + 1u);
     471                 :           0 :                 }
     472                 :           0 :             }
     473                 :         315 :         }
     474                 :          14 :     }
     475                 :             : 
     476                 :          13 :     void m_addError(std::vector<Error>& vErrs, uint32_t vOffset, const std::string& vMsg, const std::string& vExpected) const {
     477                 :          13 :         Error e;
     478                 :          13 :         e.pos.offset = vOffset;
     479                 :          13 :         m_assignLineCol(vOffset, e.pos.line, e.pos.column);
     480                 :          13 :         e.message = vMsg;
     481                 :          13 :         e.expectedHint = vExpected;
     482                 :          13 :         vErrs.push_back(e);
     483                 :          13 :     }
     484                 :             : 
     485                 :         201 :     void m_assignLineCol(uint32_t vOffset, uint32_t& vOutLine, uint32_t& vOutCol) const {
     486         [ -  + ]:         201 :         if (!computeLineColumn(vOffset, vOutLine, vOutCol)) {
     487                 :           0 :             vOutLine = 1u;
     488                 :           0 :             vOutCol = vOffset + 1u;
     489                 :           0 :         }
     490                 :         201 :     }
     491                 :             : 
     492                 :             :     // --- Lexing ---
     493                 :          14 :     void m_lex(const std::string& vSql, std::vector<Token>& vOutToks, std::vector<Error>& vOutErrs) const {
     494                 :          14 :         vOutToks.clear();
     495                 :          14 :         const uint32_t n = static_cast<uint32_t>(vSql.size());
     496                 :          14 :         uint32_t i = 0u;
     497                 :             : 
     498                 :             :         // petit lambda pour ?mettre un token
     499                 :          14 :         struct Emitter {
     500                 :          14 :             const std::string* src;
     501                 :          14 :             const Parser* self;
     502                 :          14 :             std::vector<Token>* out;
     503                 :          87 :             void emit(TokenKind k, uint32_t s, uint32_t e) {
     504                 :          87 :                 Token t;
     505                 :          87 :                 t.kind = k;
     506                 :          87 :                 t.start.offset = s;
     507                 :          87 :                 t.end.offset = e;
     508                 :          87 :                 self->m_assignLineCol(s, t.start.line, t.start.column);
     509         [ +  - ]:          87 :                 self->m_assignLineCol(e ? (e - 1u) : 0u, t.end.line, t.end.column);
     510         [ +  - ]:          87 :                 t.lex = StringRef(src->c_str() + s, (e >= s) ? (e - s) : 0u);
     511                 :          87 :                 out->push_back(t);
     512                 :          87 :             }
     513                 :          14 :         } emit = {&vSql, this, &vOutToks};
     514                 :             : 
     515         [ +  + ]:         167 :         while (i < n) {
     516                 :         153 :             char c = vSql[i];
     517                 :             : 
     518                 :             :             // espaces
     519         [ +  + ]:         153 :             if (m_isSpace(c)) {
     520                 :          66 :                 ++i;
     521                 :          66 :                 continue;
     522                 :          66 :             }
     523                 :             : 
     524                 :             :             // commentaires --
     525         [ -  + ]:          87 :             if (c == '-') {
     526 [ #  # ][ #  # ]:           0 :                 if (i + 1u < n && vSql[i + 1u] == '-') {
     527                 :           0 :                     i += 2u;
     528 [ #  # ][ #  # ]:           0 :                     while (i < n && vSql[i] != '\n' && vSql[i] != '\r')
                 [ #  # ]
     529                 :           0 :                         ++i;
     530                 :           0 :                     continue;
     531                 :           0 :                 }
     532                 :           0 :             }
     533                 :             :             // commentaires /* ... */
     534         [ -  + ]:          87 :             if (c == '/') {
     535 [ #  # ][ #  # ]:           0 :                 if (i + 1u < n && vSql[i + 1u] == '*') {
     536                 :           0 :                     uint32_t depth = 1u;
     537                 :           0 :                     i += 2u;
     538 [ #  # ][ #  # ]:           0 :                     while (i < n && depth > 0u) {
     539 [ #  # ][ #  # ]:           0 :                         if (vSql[i] == '/' && i + 1u < n && vSql[i + 1u] == '*') {
                 [ #  # ]
     540         [ #  # ]:           0 :                             if (m_options.allowNestedBlockComments) {
     541                 :           0 :                                 ++depth;
     542                 :           0 :                                 i += 2u;
     543                 :           0 :                                 continue;
     544                 :           0 :                             }
     545                 :           0 :                         }
     546 [ #  # ][ #  # ]:           0 :                         if (vSql[i] == '*' && i + 1u < n && vSql[i + 1u] == '/') {
                 [ #  # ]
     547                 :           0 :                             --depth;
     548                 :           0 :                             i += 2u;
     549                 :           0 :                             continue;
     550                 :           0 :                         }
     551                 :           0 :                         ++i;
     552                 :           0 :                     }
     553         [ #  # ]:           0 :                     if (depth > 0u) {
     554         [ #  # ]:           0 :                         m_addError(vOutErrs, i ? (i - 1u) : 0u, "comment /* ... */ not closed", "");
     555                 :           0 :                     }
     556                 :           0 :                     continue;
     557                 :           0 :                 }
     558                 :           0 :             }
     559                 :             : 
     560                 :             :             // cha?nes '...'
     561         [ -  + ]:          87 :             if (c == '\'') {
     562                 :           0 :                 const uint32_t s = i++;
     563                 :           0 :                 bool closed = false;
     564         [ #  # ]:           0 :                 while (i < n) {
     565         [ #  # ]:           0 :                     if (vSql[i] == '\'') {
     566 [ #  # ][ #  # ]:           0 :                         if (i + 1u < n && vSql[i + 1u] == '\'') {
     567                 :           0 :                             i += 2u;
     568                 :           0 :                             continue;
     569                 :           0 :                         }  // quote doubl?e
     570                 :           0 :                         ++i;
     571                 :           0 :                         closed = true;
     572                 :           0 :                         break;
     573                 :           0 :                     }
     574                 :           0 :                     ++i;
     575                 :           0 :                 }
     576         [ #  # ]:           0 :                 if (!closed) {
     577                 :           0 :                     m_addError(vOutErrs, s, "string not close", "expected: '");
     578                 :           0 :                     emit.emit(TokenKind::String, s, n);
     579                 :           0 :                 } else {
     580                 :           0 :                     emit.emit(TokenKind::String, s, i);
     581                 :           0 :                 }
     582                 :           0 :                 continue;
     583                 :           0 :             }
     584                 :             : 
     585                 :             :             // blob X'ABCD'
     586 [ -  + ][ +  + ]:          87 :             if (c == 'X' || c == 'x') {
     587 [ +  - ][ -  + ]:           4 :                 if (i + 1u < n && vSql[i + 1u] == '\'') {
     588                 :           0 :                     const uint32_t s = i;
     589                 :           0 :                     i += 2u;
     590                 :           0 :                     bool closed = false, badHex = false;
     591                 :           0 :                     uint32_t hexCount = 0u;
     592         [ #  # ]:           0 :                     while (i < n) {
     593         [ #  # ]:           0 :                         if (vSql[i] == '\'') {
     594                 :           0 :                             ++i;
     595                 :           0 :                             closed = true;
     596                 :           0 :                             break;
     597                 :           0 :                         }
     598         [ #  # ]:           0 :                         if (!m_isHex(vSql[i])) {
     599                 :           0 :                             badHex = true;
     600                 :           0 :                             ++i;
     601                 :           0 :                             continue;
     602                 :           0 :                         }
     603                 :           0 :                         ++hexCount;
     604                 :           0 :                         ++i;
     605                 :           0 :                     }
     606                 :           0 :                     emit.emit(TokenKind::Blob, s, i);
     607         [ #  # ]:           0 :                     if (!closed) {
     608                 :           0 :                         m_addError(vOutErrs, s, "blob not closed", "expected: '");
     609         [ #  # ]:           0 :                     } else if ((hexCount % 2u) != 0u) {
     610                 :           0 :                         m_addError(vOutErrs, s, "blob hexa of odd length", "");
     611         [ #  # ]:           0 :                     } else if (badHex) {
     612                 :           0 :                         m_addError(vOutErrs, s, "char not-hexa in blob", "");
     613                 :           0 :                     }
     614                 :           0 :                     continue;
     615                 :           0 :                 }
     616                 :           4 :             }
     617                 :             : 
     618                 :             :             // param?tres
     619 [ -  + ][ -  + ]:          87 :             if (c == '?' || c == ':' || c == '@' || c == '$') {
         [ -  + ][ -  + ]
     620                 :           0 :                 const uint32_t s = i++;
     621         [ #  # ]:           0 :                 if (c == '?') {
     622 [ #  # ][ #  # ]:           0 :                     while (i < n && m_isDigit(vSql[i]))
     623                 :           0 :                         ++i;  // ?123
     624                 :           0 :                 } else {
     625 [ #  # ][ #  # ]:           0 :                     while (i < n && (m_isAlnum(vSql[i]) || vSql[i] == '_'))
                 [ #  # ]
     626                 :           0 :                         ++i;  // :name @name $name
     627                 :           0 :                 }
     628                 :           0 :                 emit.emit(TokenKind::Parameter, s, i);
     629                 :           0 :                 continue;
     630                 :           0 :             }
     631                 :             : 
     632                 :             :             // nombres
     633 [ +  + ][ -  + ]:          87 :             if (m_isDigit(c) || (c == '.' && i + 1u < n && m_isDigit(vSql[i + 1u]))) {
         [ #  # ][ #  # ]
     634                 :          10 :                 const uint32_t s = i;
     635                 :          10 :                 bool hasDot = false;
     636         [ -  + ]:          10 :                 if (c == '.') {
     637                 :           0 :                     hasDot = true;
     638                 :           0 :                     ++i;
     639                 :           0 :                 }
     640 [ +  - ][ +  + ]:          20 :                 while (i < n && m_isDigit(vSql[i]))
     641                 :          10 :                     ++i;
     642 [ +  - ][ -  + ]:          10 :                 if (i < n && vSql[i] == '.' && !hasDot) {
                 [ #  # ]
     643                 :           0 :                     hasDot = true;
     644                 :           0 :                     ++i;
     645 [ #  # ][ #  # ]:           0 :                     while (i < n && m_isDigit(vSql[i]))
     646                 :           0 :                         ++i;
     647                 :           0 :                 }
     648 [ +  - ][ -  + ]:          10 :                 if (i < n && (vSql[i] == 'e' || vSql[i] == 'E')) {
                 [ -  + ]
     649                 :           0 :                     ++i;
     650 [ #  # ][ #  # ]:           0 :                     if (i < n && (vSql[i] == '+' || vSql[i] == '-'))
                 [ #  # ]
     651                 :           0 :                         ++i;
     652 [ #  # ][ #  # ]:           0 :                     while (i < n && m_isDigit(vSql[i]))
     653                 :           0 :                         ++i;
     654                 :           0 :                 }
     655                 :          10 :                 emit.emit(TokenKind::Number, s, i);
     656                 :          10 :                 continue;
     657                 :          10 :             }
     658                 :             : 
     659                 :             :             // identifiers quot?s "..."
     660         [ -  + ]:          77 :             if (c == '"') {
     661                 :           0 :                 const uint32_t s = i++;
     662                 :           0 :                 bool closed = false;
     663         [ #  # ]:           0 :                 while (i < n) {
     664         [ #  # ]:           0 :                     if (vSql[i] == '"') {
     665 [ #  # ][ #  # ]:           0 :                         if (i + 1u < n && vSql[i + 1u] == '"') {
     666                 :           0 :                             i += 2u;
     667                 :           0 :                             continue;
     668                 :           0 :                         }
     669                 :           0 :                         ++i;
     670                 :           0 :                         closed = true;
     671                 :           0 :                         break;
     672                 :           0 :                     }
     673                 :           0 :                     ++i;
     674                 :           0 :                 }
     675         [ #  # ]:           0 :                 if (!closed) {
     676                 :           0 :                     m_addError(vOutErrs, s, "identifier \"...\" not closed", "expected: \"");
     677                 :           0 :                 }
     678                 :           0 :                 emit.emit(TokenKind::Identifier, s, i);
     679                 :           0 :                 continue;
     680                 :           0 :             }
     681                 :             :             // identifiers `...` or [ ... ]
     682 [ -  + ][ -  + ]:          77 :             if (c == '`' || c == '[') {
     683         [ #  # ]:           0 :                 const char closing = (c == '`') ? '`' : ']';
     684                 :           0 :                 const uint32_t s = i++;
     685                 :           0 :                 bool closed = false;
     686         [ #  # ]:           0 :                 while (i < n) {
     687         [ #  # ]:           0 :                     if (vSql[i] == closing) {
     688                 :           0 :                         ++i;
     689                 :           0 :                         closed = true;
     690                 :           0 :                         break;
     691                 :           0 :                     }
     692                 :           0 :                     ++i;
     693                 :           0 :                 }
     694         [ #  # ]:           0 :                 if (!closed) {
     695                 :           0 :                     std::string msg("identifier not closed (expected: ");
     696                 :           0 :                     msg.push_back(closing);
     697                 :           0 :                     msg.push_back(')');
     698                 :           0 :                     m_addError(vOutErrs, s, msg, "");
     699                 :           0 :                 }
     700                 :           0 :                 emit.emit(TokenKind::Identifier, s, i);
     701                 :           0 :                 continue;
     702                 :           0 :             }
     703                 :             : 
     704                 :             :             // clang-format off
     705                 :             :             // identifiers / mots-cl?s non quot?s
     706         [ +  + ]:          77 :             if (m_isAlpha(c)) {
     707                 :          51 :                 const uint32_t s = i++;
     708 [ +  - ][ +  + ]:         213 :                 while (i<n && (m_isAlnum(vSql[i]) || vSql[i]=='$')) ++i;
                 [ -  + ]
     709                 :             : 
     710         [ +  - ]:          51 :                 StringRef v(vSql.c_str() + s, (i >= s) ? (i - s) : 0u);
     711                 :          51 :                 TokenKind k = TokenKind::Identifier;
     712                 :             : 
     713         [ +  + ]:          51 :                 if (m_ieq(v,"SELECT")) k = TokenKind::KwSelect;
     714         [ +  + ]:          43 :                 else if (m_ieq(v,"FROM")) k = TokenKind::KwFrom;
     715         [ +  + ]:          36 :                 else if (m_ieq(v,"WHERE")) k = TokenKind::KwWhere;
     716         [ -  + ]:          35 :                 else if (m_ieq(v,"GROUP")) k = TokenKind::KwGroup;
     717         [ -  + ]:          35 :                 else if (m_ieq(v,"BY")) k = TokenKind::KwBy;
     718         [ -  + ]:          35 :                 else if (m_ieq(v,"HAVING")) k = TokenKind::KwHaving;
     719         [ +  + ]:          35 :                 else if (m_ieq(v,"ORDER")) k = TokenKind::KwOrder;
     720         [ +  + ]:          34 :                 else if (m_ieq(v,"LIMIT")) k = TokenKind::KwLimit;
     721         [ -  + ]:          33 :                 else if (m_ieq(v,"OFFSET")) k = TokenKind::KwOffset;
     722         [ -  + ]:          33 :                 else if (m_ieq(v,"WITH")) k = TokenKind::KwWith;
     723         [ -  + ]:          33 :                 else if (m_ieq(v,"AS")) k = TokenKind::KwAs;
     724         [ +  + ]:          33 :                 else if (m_ieq(v,"INSERT")) k = TokenKind::KwInsert;
     725         [ +  + ]:          31 :                 else if (m_ieq(v,"INTO")) k = TokenKind::KwInto;
     726         [ +  + ]:          30 :                 else if (m_ieq(v,"VALUES")) k = TokenKind::KwValues;
     727         [ +  + ]:          28 :                 else if (m_ieq(v,"UPDATE")) k = TokenKind::KwUpdate;
     728         [ -  + ]:          27 :                 else if (m_ieq(v,"SET")) k = TokenKind::KwSet;
     729         [ +  + ]:          27 :                 else if (m_ieq(v,"DELETE")) k = TokenKind::KwDelete;
     730         [ +  + ]:          26 :                 else if (m_ieq(v,"CREATE")) k = TokenKind::KwCreate;
     731         [ +  + ]:          24 :                 else if (m_ieq(v,"TABLE")) k = TokenKind::KwTable;
     732         [ -  + ]:          22 :                 else if (m_ieq(v,"IF")) k = TokenKind::KwIf;
     733         [ -  + ]:          22 :                 else if (m_ieq(v,"NOT")) k = TokenKind::KwNot;
     734         [ -  + ]:          22 :                 else if (m_ieq(v,"EXISTS")) k = TokenKind::KwExists;
     735         [ +  + ]:          22 :                 else if (m_ieq(v,"PRIMARY")) k = TokenKind::KwPrimary;
     736         [ +  + ]:          21 :                 else if (m_ieq(v,"KEY")) k = TokenKind::KwKey;
     737         [ -  + ]:          20 :                 else if (m_ieq(v,"UNIQUE")) k = TokenKind::KwUnique;
     738         [ -  + ]:          20 :                 else if (m_ieq(v,"CHECK")) k = TokenKind::KwCheck;
     739         [ -  + ]:          20 :                 else if (m_ieq(v,"REFERENCES")) k = TokenKind::KwReferences;
     740         [ -  + ]:          20 :                 else if (m_ieq(v,"WITHOUT")) k = TokenKind::KwWithout;
     741         [ -  + ]:          20 :                 else if (m_ieq(v,"ROWID")) k = TokenKind::KwRowid;
     742         [ -  + ]:          20 :                 else if (m_ieq(v,"ON")) k = TokenKind::KwOn;
     743         [ -  + ]:          20 :                 else if (m_ieq(v,"CONFLICT")) k = TokenKind::KwConflict;
     744                 :             : 
     745                 :          51 :                 emit.emit(k, s, i);
     746                 :          51 :                 continue;
     747                 :          51 :             }
     748                 :             : 
     749                 :             :             // op?rateurs / ponctuation
     750         [ +  + ]:          26 :             if (i+1u<n) {
     751                 :          14 :                 char c2 = vSql[i+1u];
     752 [ -  + ][ #  # ]:          14 :                 if (c=='|' && c2=='|') { emit.emit(TokenKind::PipePipe, i, i+2u); i+=2u; continue; }
     753 [ -  + ][ #  # ]:          14 :                 if (c=='<' && c2=='<') { emit.emit(TokenKind::Shl, i, i+2u); i+=2u; continue; }
     754 [ -  + ][ #  # ]:          14 :                 if (c=='>' && c2=='>') { emit.emit(TokenKind::Shr, i, i+2u); i+=2u; continue; }
     755 [ +  + ][ -  + ]:          14 :                 if (c=='=' && c2=='=') { emit.emit(TokenKind::EqEq, i, i+2u); i+=2u; continue; }
     756 [ -  + ][ #  # ]:          14 :                 if (c=='!' && c2=='=') { emit.emit(TokenKind::Ne, i, i+2u); i+=2u; continue; }
     757 [ -  + ][ #  # ]:          14 :                 if (c=='<' && c2=='>') { emit.emit(TokenKind::Ne2, i, i+2u); i+=2u; continue; }
     758 [ -  + ][ #  # ]:          14 :                 if (c=='<' && c2=='=') { emit.emit(TokenKind::Le, i, i+2u); i+=2u; continue; }
     759 [ -  + ][ #  # ]:          14 :                 if (c=='>' && c2=='=') { emit.emit(TokenKind::Ge, i, i+2u); i+=2u; continue; }
     760                 :          14 :             }
     761                 :          26 :             switch (c) {
     762         [ -  + ]:           0 :                 case '+': emit.emit(TokenKind::Plus, i, i+1u); ++i; continue;
     763         [ -  + ]:           0 :                 case '-': emit.emit(TokenKind::Minus, i, i+1u); ++i; continue;
     764         [ -  + ]:           0 :                 case '*': emit.emit(TokenKind::Star, i, i+1u); ++i; continue;
     765         [ -  + ]:           0 :                 case '/': emit.emit(TokenKind::Slash, i, i+1u); ++i; continue;
     766         [ -  + ]:           0 :                 case '%': emit.emit(TokenKind::Percent, i, i+1u); ++i; continue;
     767         [ -  + ]:           0 :                 case '&': emit.emit(TokenKind::Amp, i, i+1u); ++i; continue;
     768         [ -  + ]:           0 :                 case '|': emit.emit(TokenKind::Pipe, i, i+1u); ++i; continue;
     769         [ -  + ]:           0 :                 case '~': emit.emit(TokenKind::Tilde, i, i+1u); ++i; continue;
     770         [ +  + ]:           1 :                 case '=': emit.emit(TokenKind::Assign, i, i+1u); ++i; continue;
     771         [ -  + ]:           0 :                 case '<': emit.emit(TokenKind::Lt, i, i+1u); ++i; continue;
     772         [ -  + ]:           0 :                 case '>': emit.emit(TokenKind::Gt, i, i+1u); ++i; continue;
     773         [ +  + ]:           5 :                 case ',': emit.emit(TokenKind::Comma, i, i+1u); ++i; continue;
     774         [ -  + ]:           0 :                 case '.': emit.emit(TokenKind::Dot, i, i+1u); ++i; continue;
     775         [ +  + ]:           3 :                 case '(': emit.emit(TokenKind::LParen, i, i+1u); ++i; continue;
     776         [ +  + ]:           3 :                 case ')': emit.emit(TokenKind::RParen, i, i+1u); ++i; continue;
     777         [ +  + ]:          14 :                 case ';': emit.emit(TokenKind::Semicolon, i, i+1u); ++i; continue;
     778         [ -  + ]:           0 :                 default: break;
     779                 :          26 :             }
     780                 :             :             // clang-format on
     781                 :             : 
     782                 :             :             // inconnu
     783                 :           0 :             m_addError(vOutErrs, i, "char unknown", "");
     784                 :           0 :             emit.emit(TokenKind::Unknown, i, i + 1u);
     785                 :           0 :             ++i;
     786                 :           0 :         }
     787                 :             : 
     788                 :             :         // EOF
     789                 :          14 :         Token eof;
     790                 :          14 :         eof.kind = TokenKind::EndOfFile;
     791                 :          14 :         eof.start.offset = n;
     792                 :          14 :         eof.end.offset = n;
     793                 :          14 :         m_assignLineCol(n, eof.start.line, eof.start.column);
     794                 :          14 :         eof.end = eof.start;
     795                 :          14 :         eof.lex = StringRef(NULL, 0u);
     796                 :          14 :         vOutToks.push_back(eof);
     797                 :          14 :     }
     798                 :             : 
     799                 :          14 :     void m_splitStatements(const std::vector<Token>& vToks, std::vector<StatementRange>& vOut) const {
     800                 :          14 :         vOut.clear();
     801                 :          14 :         uint32_t curStart = 0u;
     802                 :          14 :         uint32_t lastNonSpace = 0u;
     803                 :          14 :         bool hasContent = false;
     804                 :             : 
     805         [ +  - ]:         101 :         for (size_t i = 0; i < vToks.size(); ++i) {
     806                 :         101 :             const Token& t = vToks[i];
     807         [ +  + ]:         101 :             if (t.kind == TokenKind::EndOfFile) {
     808         [ -  + ]:          14 :                 if (hasContent) {
     809                 :           0 :                     StatementRange r;
     810                 :           0 :                     r.beginOffset = curStart;
     811                 :           0 :                     r.endOffset = lastNonSpace + 1u;
     812         [ #  # ]:           0 :                     if (r.endOffset <= r.beginOffset)
     813                 :           0 :                         r.endOffset = r.beginOffset;
     814                 :           0 :                     vOut.push_back(r);
     815                 :           0 :                 }
     816                 :          14 :                 break;
     817                 :          14 :             }
     818                 :             :             // pas d'espaces ici (d?j? consomm?s au lexing)
     819         [ +  + ]:          87 :             if (!hasContent) {
     820                 :          14 :                 hasContent = true;
     821                 :          14 :                 curStart = t.start.offset;
     822                 :          14 :             }
     823         [ +  - ]:          87 :             lastNonSpace = (t.end.offset > 0u) ? (t.end.offset - 1u) : t.end.offset;
     824                 :             : 
     825         [ +  + ]:          87 :             if (t.kind == TokenKind::Semicolon) {
     826         [ +  - ]:          14 :                 if (hasContent) {
     827                 :          14 :                     StatementRange r;
     828                 :          14 :                     r.beginOffset = curStart;
     829                 :          14 :                     r.endOffset = t.start.offset;  // before le ';'
     830         [ -  + ]:          14 :                     if (r.endOffset < r.beginOffset)
     831                 :           0 :                         r.endOffset = r.beginOffset;
     832                 :          14 :                     vOut.push_back(r);
     833                 :          14 :                 }
     834                 :          14 :                 hasContent = false;
     835                 :          14 :             }
     836                 :          87 :         }
     837                 :          14 :     }
     838                 :             : 
     839                 :          14 :     StatementKind m_detectKind(const std::vector<Token>& vToks, const StatementRange& vRng) const {
     840         [ +  - ]:          27 :         for (size_t i = 0; i < vToks.size(); ++i) {
     841                 :          27 :             const Token& t = vToks[i];
     842         [ +  + ]:          27 :             if (t.start.offset < vRng.beginOffset)
     843                 :          13 :                 continue;
     844         [ -  + ]:          14 :             if (t.start.offset >= vRng.endOffset)
     845                 :           0 :                 break;
     846                 :          14 :             switch (t.kind) {
     847         [ +  + ]:           8 :                 case TokenKind::KwSelect: return StatementKind::Select;
     848         [ +  + ]:           2 :                 case TokenKind::KwInsert: return StatementKind::Insert;
     849         [ +  + ]:           1 :                 case TokenKind::KwUpdate: return StatementKind::Update;
     850         [ +  + ]:           1 :                 case TokenKind::KwDelete: return StatementKind::Delete;
     851         [ +  + ]:           2 :                 case TokenKind::KwCreate: return StatementKind::CreateTable;  // affin? in check
     852         [ -  + ]:           0 :                 default: return StatementKind::Other;
     853                 :          14 :             }
     854                 :          14 :         }
     855                 :           0 :         return StatementKind::Other;
     856                 :          14 :     }
     857                 :             : 
     858                 :             :     // --- v?rifs g?n?rales
     859                 :          14 :     void m_checkParens(const std::vector<Token>& vToks, const StatementRange& vRng, Report& vOut) const {
     860                 :          14 :         int32_t depth = 0;
     861         [ +  - ]:         100 :         for (size_t i = 0; i < vToks.size(); ++i) {
     862                 :         100 :             const Token& t = vToks[i];
     863         [ +  + ]:         100 :             if (t.start.offset < vRng.beginOffset)
     864                 :          13 :                 continue;
     865         [ +  + ]:          87 :             if (t.start.offset >= vRng.endOffset)
     866                 :          14 :                 break;
     867         [ +  + ]:          73 :             if (t.kind == TokenKind::LParen) {
     868                 :           3 :                 ++depth;
     869         [ +  + ]:          70 :             } else if (t.kind == TokenKind::RParen) {
     870                 :           3 :                 --depth;
     871         [ +  + ]:           3 :                 if (depth < 0) {
     872                 :           1 :                     m_addError(vOut.errors, t.start.offset, "closing parenthesis without opening parenthesis", "delete ')'");
     873                 :           1 :                     depth = 0;
     874                 :           1 :                 }
     875                 :           3 :             }
     876                 :          73 :         }
     877         [ +  + ]:          14 :         if (depth > 0) {
     878                 :           1 :             m_addError(vOut.errors, vRng.endOffset, "missing closing parenthesis", "expected: ')'");
     879                 :           1 :         }
     880                 :          14 :     }
     881                 :             : 
     882                 :             :     // --- v?rifs par kind
     883                 :           2 :     void m_checkCreateTable(const std::vector<Token>& vToks, const StatementRange& vRng, const std::string& /*vSql*/, Report& vOut) const {
     884                 :           2 :         bool sawCreate = false, sawTable = false;
     885                 :           2 :         const Token* nameTok = NULL;
     886                 :           2 :         const Token* afterName = NULL;
     887                 :             : 
     888         [ +  - ]:           8 :         for (size_t i = 0; i < vToks.size(); ++i) {
     889                 :           8 :             const Token& t = vToks[i];
     890         [ -  + ]:           8 :             if (t.start.offset < vRng.beginOffset)
     891                 :           0 :                 continue;
     892         [ -  + ]:           8 :             if (t.start.offset >= vRng.endOffset)
     893                 :           0 :                 break;
     894                 :             : 
     895         [ +  + ]:           8 :             if (!sawCreate) {
     896         [ +  - ]:           2 :                 if (t.kind == TokenKind::KwCreate) {
     897                 :           2 :                     sawCreate = true;
     898                 :           2 :                 } else {
     899                 :           0 :                     return;
     900                 :           0 :                 }
     901                 :           2 :                 continue;
     902                 :           2 :             }
     903         [ +  + ]:           6 :             if (!sawTable) {
     904         [ +  - ]:           2 :                 if (t.kind == TokenKind::KwTable) {
     905                 :           2 :                     sawTable = true;
     906                 :           2 :                 }
     907                 :           2 :                 continue;
     908                 :           2 :             }
     909         [ +  + ]:           4 :             if (!nameTok) {
     910         [ +  - ]:           2 :                 if (t.kind == TokenKind::Identifier) {
     911                 :           2 :                     nameTok = &t;
     912                 :           2 :                     continue;
     913                 :           2 :                 }
     914         [ #  # ]:           0 :                 if (t.kind == TokenKind::KwIf)
     915                 :           0 :                     continue;
     916         [ #  # ]:           0 :                 if (t.kind == TokenKind::KwNot)
     917                 :           0 :                     continue;
     918         [ #  # ]:           0 :                 if (t.kind == TokenKind::KwExists)
     919                 :           0 :                     continue;
     920                 :           0 :                 m_addError(vOut.errors, t.start.offset, "table name expected after CREATE TABLE", "identifier");
     921                 :           0 :                 return;
     922                 :           2 :             } else {
     923                 :           2 :                 afterName = &t;
     924                 :           2 :                 break;
     925                 :           2 :             }
     926                 :           4 :         }
     927                 :             : 
     928         [ -  + ]:           2 :         if (!nameTok) {
     929                 :           0 :             m_addError(vOut.errors, vRng.beginOffset, "table name missing", "identifier");
     930                 :           0 :             return;
     931                 :           0 :         }
     932         [ -  + ]:           2 :         if (!afterName) {
     933                 :           0 :             m_addError(vOut.errors, nameTok->end.offset, "expected '(' or AS after table name", "(' | AS");
     934                 :           0 :             return;
     935                 :           0 :         }
     936                 :             : 
     937                 :           2 :         bool hasParen = false, hasAs = false;
     938         [ +  - ]:           9 :         for (size_t i = 0; i < vToks.size(); ++i) {
     939                 :           9 :             const Token& t = vToks[i];
     940         [ +  + ]:           9 :             if (t.start.offset < afterName->start.offset)
     941                 :           6 :                 continue;
     942         [ +  + ]:           3 :             if (t.start.offset >= vRng.endOffset)
     943                 :           1 :                 break;
     944         [ +  + ]:           2 :             if (t.kind == TokenKind::LParen) {
     945                 :           1 :                 hasParen = true;
     946                 :           1 :                 break;
     947                 :           1 :             }
     948         [ -  + ]:           1 :             if (t.kind == TokenKind::KwAs) {
     949                 :           0 :                 hasAs = true;
     950                 :           0 :                 break;
     951                 :           0 :             }
     952                 :           1 :         }
     953 [ +  + ][ +  - ]:           2 :         if (!hasParen && !hasAs) {
     954                 :           1 :             m_addError(vOut.errors, afterName->start.offset, "expected '(' or AS after table name", "(' | AS");
     955                 :           1 :         }
     956                 :           2 :     }
     957                 :             : 
     958                 :           2 :     void m_checkInsert(const std::vector<Token>& vToks, const StatementRange& vRng, Report& vOut) const {
     959                 :           2 :         bool sawInsert = false, sawInto = false, sawValues = false, sawSelect = false;
     960                 :           2 :         const Token* afterValues = NULL;
     961                 :             : 
     962         [ +  - ]:          12 :         for (size_t i = 0; i < vToks.size(); ++i) {
     963                 :          12 :             const Token& t = vToks[i];
     964         [ -  + ]:          12 :             if (t.start.offset < vRng.beginOffset)
     965                 :           0 :                 continue;
     966         [ +  + ]:          12 :             if (t.start.offset >= vRng.endOffset)
     967                 :           1 :                 break;
     968                 :             : 
     969         [ +  + ]:          11 :             if (!sawInsert) {
     970         [ +  - ]:           2 :                 if (t.kind == TokenKind::KwInsert) {
     971                 :           2 :                     sawInsert = true;
     972                 :           2 :                 } else {
     973                 :           0 :                     return;
     974                 :           0 :                 }
     975                 :           2 :                 continue;
     976                 :           2 :             }
     977         [ +  + ]:           9 :             if (!sawInto) {
     978         [ +  + ]:           6 :                 if (t.kind == TokenKind::KwInto) {
     979                 :           1 :                     sawInto = true;
     980                 :           1 :                     continue;
     981                 :           1 :                 }
     982                 :           5 :                 continue;
     983                 :           6 :             }
     984 [ +  + ][ +  - ]:           3 :             if (!sawValues && !sawSelect) {
     985         [ +  + ]:           2 :                 if (t.kind == TokenKind::KwValues) {
     986                 :           1 :                     sawValues = true;
     987                 :           1 :                     continue;
     988                 :           1 :                 }
     989         [ -  + ]:           1 :                 if (t.kind == TokenKind::KwSelect) {
     990                 :           0 :                     sawSelect = true;
     991                 :           0 :                     continue;
     992                 :           0 :                 }
     993                 :           1 :                 continue;
     994                 :           1 :             }
     995 [ +  - ][ +  - ]:           1 :             if (sawValues && !afterValues) {
     996                 :           1 :                 afterValues = &t;
     997                 :           1 :                 break;
     998                 :           1 :             }
     999                 :           1 :         }
    1000                 :             : 
    1001         [ -  + ]:           2 :         if (!sawInsert)
    1002                 :           0 :             return;
    1003         [ +  + ]:           2 :         if (!sawInto) {
    1004                 :           1 :             m_addError(vOut.errors, vRng.beginOffset, "keyword INTO missing in INSERT", "INTO");
    1005                 :           1 :             return;
    1006                 :           1 :         }
    1007 [ -  + ][ #  # ]:           1 :         if (!sawValues && !sawSelect) {
    1008                 :           0 :             m_addError(vOut.errors, vRng.beginOffset, "INSERT incomplete", "VALUES | SELECT");
    1009                 :           0 :             return;
    1010                 :           0 :         }
    1011 [ +  - ][ +  - ]:           1 :         if (sawValues && afterValues) {
    1012                 :           1 :             int32_t depth = 0;
    1013                 :           1 :             bool hasPar = false;
    1014         [ +  - ]:          10 :             for (size_t i = 0; i < vToks.size(); ++i) {
    1015                 :          10 :                 const Token& t = vToks[i];
    1016         [ +  + ]:          10 :                 if (t.start.offset < afterValues->start.offset)
    1017                 :           4 :                     continue;
    1018         [ +  + ]:           6 :                 if (t.start.offset >= vRng.endOffset)
    1019                 :           1 :                     break;
    1020         [ -  + ]:           5 :                 if (t.kind == TokenKind::LParen) {
    1021                 :           0 :                     ++depth;
    1022                 :           0 :                     hasPar = true;
    1023         [ -  + ]:           5 :                 } else if (t.kind == TokenKind::RParen) {
    1024                 :           0 :                     --depth;
    1025         [ #  # ]:           0 :                     if (depth < 0)
    1026                 :           0 :                         depth = 0;
    1027                 :           0 :                 }
    1028                 :           5 :             }
    1029         [ +  - ]:           1 :             if (!hasPar) {
    1030                 :           1 :                 m_addError(vOut.errors, afterValues->start.offset, "VALUES without parentheses list", "('...')");
    1031                 :           1 :             }
    1032                 :           1 :         }
    1033                 :           1 :     }
    1034                 :             : 
    1035                 :           1 :     void m_checkUpdate(const std::vector<Token>& vToks, const StatementRange& vRng, Report& vOut) const {
    1036                 :           1 :         bool sawUpdate = false, sawSet = false;
    1037         [ +  - ]:           7 :         for (size_t i = 0; i < vToks.size(); ++i) {
    1038                 :           7 :             const Token& t = vToks[i];
    1039         [ -  + ]:           7 :             if (t.start.offset < vRng.beginOffset)
    1040                 :           0 :                 continue;
    1041         [ +  + ]:           7 :             if (t.start.offset >= vRng.endOffset)
    1042                 :           1 :                 break;
    1043         [ +  + ]:           6 :             if (!sawUpdate) {
    1044         [ +  - ]:           1 :                 if (t.kind == TokenKind::KwUpdate) {
    1045                 :           1 :                     sawUpdate = true;
    1046                 :           1 :                 } else {
    1047                 :           0 :                     return;
    1048                 :           0 :                 }
    1049                 :           1 :                 continue;
    1050                 :           1 :             }
    1051         [ +  - ]:           5 :             if (!sawSet) {
    1052         [ -  + ]:           5 :                 if (t.kind == TokenKind::KwSet) {
    1053                 :           0 :                     sawSet = true;
    1054                 :           0 :                     break;
    1055                 :           0 :                 }
    1056                 :           5 :             }
    1057                 :           5 :         }
    1058         [ -  + ]:           1 :         if (!sawUpdate)
    1059                 :           0 :             return;
    1060         [ +  - ]:           1 :         if (!sawSet) {
    1061                 :           1 :             m_addError(vOut.errors, vRng.beginOffset, "UPDATE without SET", "SET");
    1062                 :           1 :         }
    1063                 :           1 :     }
    1064                 :             : 
    1065                 :           1 :     void m_checkDelete(const std::vector<Token>& vToks, const StatementRange& vRng, Report& vOut) const {
    1066                 :           1 :         bool sawDelete = false, sawFrom = false;
    1067         [ +  - ]:           3 :         for (size_t i = 0; i < vToks.size(); ++i) {
    1068                 :           3 :             const Token& t = vToks[i];
    1069         [ -  + ]:           3 :             if (t.start.offset < vRng.beginOffset)
    1070                 :           0 :                 continue;
    1071         [ +  + ]:           3 :             if (t.start.offset >= vRng.endOffset)
    1072                 :           1 :                 break;
    1073         [ +  + ]:           2 :             if (!sawDelete) {
    1074         [ +  - ]:           1 :                 if (t.kind == TokenKind::KwDelete) {
    1075                 :           1 :                     sawDelete = true;
    1076                 :           1 :                 } else {
    1077                 :           0 :                     return;
    1078                 :           0 :                 }
    1079                 :           1 :                 continue;
    1080                 :           1 :             }
    1081         [ +  - ]:           1 :             if (!sawFrom) {
    1082         [ -  + ]:           1 :                 if (t.kind == TokenKind::KwFrom) {
    1083                 :           0 :                     sawFrom = true;
    1084                 :           0 :                     break;
    1085                 :           0 :                 }
    1086                 :           1 :             }
    1087                 :           1 :         }
    1088         [ -  + ]:           1 :         if (!sawDelete)
    1089                 :           0 :             return;
    1090         [ +  - ]:           1 :         if (!sawFrom) {
    1091                 :           1 :             m_addError(vOut.errors, vRng.beginOffset, "DELETE without FROM", "FROM");
    1092                 :           1 :         }
    1093                 :           1 :     }
    1094                 :             : 
    1095                 :           8 :     void m_checkSelect(const std::vector<Token>& vToks, const StatementRange& vRng, Report& vOut) const {
    1096                 :             :         // 1) Trouver SELECT
    1097                 :           8 :         size_t selIdx = static_cast<size_t>(-1);
    1098         [ +  - ]:          21 :         for (size_t i = 0u; i < vToks.size(); ++i) {
    1099                 :          21 :             const Token& t = vToks[i];
    1100         [ +  + ]:          21 :             if (t.start.offset < vRng.beginOffset) {
    1101                 :          13 :                 continue;
    1102                 :          13 :             }
    1103         [ -  + ]:           8 :             if (t.start.offset >= vRng.endOffset) {
    1104                 :           0 :                 break;
    1105                 :           0 :             }
    1106         [ +  - ]:           8 :             if (t.kind == TokenKind::KwSelect) {
    1107                 :           8 :                 selIdx = i;
    1108                 :           8 :                 break;
    1109                 :           8 :             }
    1110                 :           8 :         }
    1111         [ -  + ]:           8 :         if (selIdx == static_cast<size_t>(-1)) {
    1112                 :           0 :             return;  // pas un SELECT (s?curit?)
    1113                 :           0 :         }
    1114                 :             : 
    1115                 :             :         // 2) Scanner la projection: SELECT <expr> [, <expr> ...] [FROM ... | ...]
    1116                 :           8 :         bool seenAny = false;
    1117                 :           8 :         bool expectingExpr = true;  // vrai au d?but / after chaque virgule
    1118                 :           8 :         size_t i = selIdx + 1u;
    1119                 :             : 
    1120         [ +  - ]:          19 :         for (; i < vToks.size(); ++i) {
    1121                 :          19 :             const Token& t = vToks[i];
    1122         [ +  + ]:          19 :             if (t.start.offset >= vRng.endOffset) {
    1123                 :           1 :                 break;
    1124                 :           1 :             }
    1125                 :             : 
    1126                 :          18 :             const TokenKind k = t.kind;
    1127                 :             : 
    1128                 :             :             // Fin de projection : mots-cl?s majeurs or fin
    1129 [ +  + ][ -  + ]:          18 :             const bool endOfProjection = (k == TokenKind::KwFrom) || (k == TokenKind::KwWhere) || (k == TokenKind::KwGroup) || (k == TokenKind::KwOrder) ||
         [ -  + ][ -  + ]
    1130 [ -  + ][ -  + ]:          18 :                 (k == TokenKind::KwLimit) || (k == TokenKind::KwOffset) || (k == TokenKind::Semicolon) || (k == TokenKind::EndOfFile);
         [ -  + ][ -  + ]
    1131                 :             : 
    1132         [ +  + ]:          18 :             if (endOfProjection) {
    1133         [ +  + ]:           7 :                 if (!seenAny) {
    1134                 :             :                     // Rien after SELECT
    1135                 :           1 :                     m_addError(vOut.errors, vToks[selIdx].start.offset, "projected SELECT missing", "*, identifier, expression");
    1136         [ +  + ]:           6 :                 } else if (expectingExpr) {
    1137                 :             :                     // Virgule tra?nante: ex. "SELECT id, FROM ..."
    1138                 :           2 :                     m_addError(vOut.errors, t.start.offset, "expression of projection missing before thistoken", "expression after ','");
    1139                 :           2 :                 }
    1140                 :           7 :                 break;
    1141                 :           7 :             }
    1142                 :             : 
    1143         [ +  + ]:          11 :             if (k == TokenKind::Comma) {
    1144         [ -  + ]:           2 :                 if (expectingExpr) {
    1145                 :             :                     // Cas ",," or ", FROM" (doublement signal? ici)
    1146                 :           0 :                     m_addError(vOut.errors, t.start.offset, "expression of projection missing after ','", "expression");
    1147                 :           0 :                 }
    1148                 :           2 :                 expectingExpr = true;
    1149                 :           2 :                 continue;
    1150                 :           2 :             }
    1151                 :             : 
    1152                 :             :             // T?tes possibles d'expression (simplifi?es)
    1153 [ -  + ][ +  + ]:           9 :             const bool isExprHead = (k == TokenKind::Star) || (k == TokenKind::Identifier) || (k == TokenKind::Number) || (k == TokenKind::String) ||
         [ +  + ][ -  + ]
    1154 [ -  + ][ +  + ]:           9 :                 (k == TokenKind::Parameter) || (k == TokenKind::LParen);
    1155                 :             : 
    1156         [ +  + ]:           9 :             if (isExprHead) {
    1157                 :           8 :                 seenAny = true;
    1158                 :           8 :                 expectingExpr = false;
    1159                 :           8 :                 continue;
    1160                 :           8 :             }
    1161                 :             : 
    1162                 :             :             // Token inexpected en position d'expression
    1163 [ -  + ][ #  # ]:           1 :             if (expectingExpr && !seenAny) {
    1164                 :           0 :                 m_addError(vOut.errors, t.start.offset, "token inexpected in projection SELECT", "*, identifier, expression");
    1165                 :             :                 // On continue pour tenter de rep?rer FROM/ORDER/LIMIT
    1166                 :           0 :                 expectingExpr = false;  // ?vite cascade sur ce jeton
    1167                 :           0 :                 continue;
    1168                 :           0 :             }
    1169                 :           1 :         }
    1170                 :             : 
    1171                 :             :         // Si on a fini la boucle without rencontrer fin de projection
    1172 [ -  + ][ +  + ]:           8 :         if (i >= vToks.size() || vToks[i].start.offset >= vRng.endOffset) {
    1173         [ -  + ]:           1 :             if (!seenAny) {
    1174                 :           0 :                 m_addError(vOut.errors, vToks[selIdx].start.offset, "projection SELECT missing", "*, identifier, expression");
    1175         [ -  + ]:           1 :             } else if (expectingExpr) {
    1176                 :           0 :                 m_addError(vOut.errors, vRng.endOffset, "expression of projection missing at end of SELECT", "expression after ','");
    1177                 :           0 :             }
    1178                 :           1 :         }
    1179                 :             : 
    1180                 :             :         // 3) V?rifier FROM (facultatif en SQLite, donc on ne l'exige pas, on valide seulement sa forme)
    1181         [ +  - ]:          19 :         for (size_t idx = selIdx + 1u; idx < vToks.size(); ++idx) {
    1182                 :          19 :             const Token& t = vToks[idx];
    1183         [ -  + ]:          19 :             if (t.start.offset < vRng.beginOffset) {
    1184                 :           0 :                 continue;
    1185                 :           0 :             }
    1186         [ +  + ]:          19 :             if (t.start.offset >= vRng.endOffset) {
    1187                 :           1 :                 break;
    1188                 :           1 :             }
    1189                 :             : 
    1190         [ +  + ]:          18 :             if (t.kind == TokenKind::KwFrom) {
    1191                 :           7 :                 size_t j = idx + 1u;
    1192 [ -  + ][ +  + ]:           7 :                 if (j >= vToks.size() || vToks[j].start.offset >= vRng.endOffset) {
    1193                 :           1 :                     m_addError(vOut.errors, t.start.offset, "table expectede after FROM", "identifier or sub-query");
    1194                 :           6 :                 } else {
    1195                 :           6 :                     const TokenKind nk = vToks[j].kind;
    1196 [ +  - ][ #  # ]:           6 :                     if (!(nk == TokenKind::Identifier || nk == TokenKind::LParen)) {
    1197                 :           0 :                         m_addError(vOut.errors, vToks[j].start.offset, "element invalid after FROM", "identifier or sub-query");
    1198                 :           0 :                     }
    1199                 :           6 :                 }
    1200                 :           7 :                 break;  // un seul FROM principal ici (checker l?ger)
    1201                 :           7 :             }
    1202                 :          18 :         }
    1203                 :             : 
    1204                 :             :         // 4) ORDER BY et LIMIT/OFFSET (inchang?)
    1205         [ +  - ]:          34 :         for (size_t idx = selIdx + 1u; idx < vToks.size(); ++idx) {
    1206                 :          34 :             const Token& t = vToks[idx];
    1207         [ -  + ]:          34 :             if (t.start.offset < vRng.beginOffset) {
    1208                 :           0 :                 continue;
    1209                 :           0 :             }
    1210         [ +  + ]:          34 :             if (t.start.offset >= vRng.endOffset) {
    1211                 :           8 :                 break;
    1212                 :           8 :             }
    1213                 :             : 
    1214         [ +  + ]:          26 :             if (t.kind == TokenKind::KwOrder) {
    1215                 :           1 :                 size_t j = idx + 1u;
    1216                 :           1 :                 bool hasBy = false;
    1217 [ +  - ][ -  + ]:           1 :                 while (j < vToks.size() && vToks[j].start.offset < vRng.endOffset) {
    1218         [ #  # ]:           0 :                     if (vToks[j].kind == TokenKind::KwBy) {
    1219                 :           0 :                         hasBy = true;
    1220                 :           0 :                         ++j;
    1221                 :           0 :                         break;
    1222                 :           0 :                     }
    1223         [ #  # ]:           0 :                     if (vToks[j].kind != TokenKind::Comma) {
    1224                 :           0 :                         break;
    1225                 :           0 :                     }
    1226                 :           0 :                     ++j;
    1227                 :           0 :                 }
    1228         [ +  - ]:           1 :                 if (!hasBy) {
    1229                 :           1 :                     m_addError(vOut.errors, t.start.offset, "ORDER without BY", "BY");
    1230                 :           1 :                 } else {
    1231 [ #  # ][ #  # ]:           0 :                     if (j >= vToks.size() || vToks[j].start.offset >= vRng.endOffset) {
    1232                 :           0 :                         m_addError(vOut.errors, t.start.offset, "ORDER BY incomplete", "");
    1233                 :           0 :                     }
    1234                 :           0 :                 }
    1235                 :           1 :             }
    1236                 :             : 
    1237 [ +  + ][ -  + ]:          26 :             if (t.kind == TokenKind::KwLimit || t.kind == TokenKind::KwOffset) {
    1238                 :           1 :                 size_t j = idx + 1u;
    1239 [ +  - ][ -  + ]:           1 :                 while (j < vToks.size() && vToks[j].start.offset < vRng.endOffset) {
    1240                 :           0 :                     const TokenKind k2 = vToks[j].kind;
    1241 [ #  # ][ #  # ]:           0 :                     if (k2 == TokenKind::Number || k2 == TokenKind::Parameter) {
    1242                 :           0 :                         break;
    1243                 :           0 :                     }
    1244         [ #  # ]:           0 :                     if (k2 == TokenKind::Comma) {
    1245                 :           0 :                         ++j;
    1246                 :           0 :                         continue;  // LIMIT x, y accept?
    1247                 :           0 :                     }
    1248                 :           0 :                     m_addError(vOut.errors, vToks[j].start.offset, "invalid value for LIMIT/OFFSET", "number of parameters");
    1249                 :           0 :                     break;
    1250                 :           0 :                 }
    1251 [ -  + ][ +  - ]:           1 :                 if (j >= vToks.size() || vToks[j].start.offset >= vRng.endOffset) {
    1252                 :           1 :                     m_addError(vOut.errors, t.start.offset, "LIMIT/OFFSET without value", "number of parameters");
    1253                 :           1 :                 }
    1254                 :           1 :             }
    1255                 :          26 :         }
    1256                 :           8 :     }
    1257                 :             : };
    1258                 :             : 
    1259                 :             : }  // namespace sqlite
    1260                 :             : }  // namespace ez
        

Generated by: LCOV version 2.0-1