Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  ha_duckdb_pushdown.cc   Sprache: C

 

/*
  Copyright (c) 2026, MariaDB Foundation.
  Copyright (c) 2026, Roman Nozdrin <drrtuy@gmail.com>
  Copyright (c) 2026, Leonid Fedorov.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; version 2 of the License.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA
*/


#define MYSQL_SERVER 1
#include <my_global.h>
#include "sql_class.h"
#include "sql_select.h"
#include "log.h"

#undef UNKNOWN

#include "ha_duckdb_pushdown.h"
#include "duckdb_select.h"
#include "duckdb_query.h"
#include "duckdb_context.h"
#include "cross_engine_scan.h"
#include "duckdb_config.h"
#include "duckdb_log.h"

extern handlerton *duckdb_hton;

/**
  Check whether a SELECT_LEX can be pushed down to DuckDB.

  Returns true if at least one table is DuckDB.  Non-DuckDB tables are
  collected in @p external_tables so they can be registered with the
  replacement-scan mechanism before executing the DuckDB query.
*/


static bool can_pushdown_to_duckdb(SELECT_LEX *sel_lex,
                                   std::vector<std::string> &external_tables,
                                   bool &has_duckdb_table)
{
  for (TABLE_LIST *tbl= sel_lex->get_table_list(); tbl; tbl= tbl->next_global)
  {
    if (tbl->derived || !tbl->table)
      continue;

    if (tbl->table->file->ht == duckdb_hton)
      has_duckdb_table= true;
    else
      external_tables.emplace_back(tbl->table_name.str);
  }

  return has_duckdb_table;
}

/**
  Check whether a SELECT_LEX_UNIT (UNION/EXCEPT/INTERSECT) can be
  pushed down to DuckDB.  Walks every SELECT_LEX in the unit.
*/


static bool
can_pushdown_unit_to_duckdb(SELECT_LEX_UNIT *unit,
                            std::vector<std::string> &external_tables,
                            bool &has_duckdb_table)
{
  for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select())
  {
    for (TABLE_LIST *tbl= sl->get_table_list(); tbl; tbl= tbl->next_global)
    {
      if (tbl->derived || !tbl->table)
        continue;

      if (tbl->table->file->ht == duckdb_hton)
        has_duckdb_table= true;
      else
        external_tables.emplace_back(tbl->table_name.str);
    }
  }

  return has_duckdb_table;
}

/* ----- Factory functions ----- */

static bool is_query_token(const std::string &sql, size_t start, size_t length)
{
  static const char select_kw[]= "select";
  static const char with_kw[]= "with";
  const char *keyword= length == 6 ? select_kw : length == 4 ? with_kw : nullptr;
  if (!keyword)
    return false;
  for (size_t i= 0; i < length; i++)
    if (tolower((unsigned char) sql[start + i]) != keyword[i])
      return false;
  return true;
}

static bool extract_source_query(THD *thd, std::string &source)
{
  const std::string sql(thd->query(), thd->query_length());
  if (myduck::mariadb_query_has_unsafe_quote_escape(thd, sql.data(), sql.size()))
    return false;
  const bool strip_prefix= thd->lex->sql_command == SQLCOM_INSERT_SELECT;
  const bool backslash_escapes= thd->backslash_escapes();
  size_t i= 0;
  int depth= 0;

  while (i < sql.size())
  {
    unsigned char c= (unsigned char) sql[i];
    if (isspace(c))
    {
      i++;
      continue;
    }
    size_t end;
    myduck::SqlRegionType region=
        myduck::scan_sql_region(sql, i, backslash_escapes, end);
    if (region == myduck::SqlRegionType::UNTERMINATED)
      return false;
    if (region == myduck::SqlRegionType::COMMENT)
    {
      i= end;
      continue;
    }
    if (region == myduck::SqlRegionType::QUOTED)
    {
      if (!strip_prefix && depth == 0)
        return false;
      i= end;
      continue;
    }
    if (c == '(')
    {
      if (!strip_prefix && depth == 0)
        return false;
      depth++;
      i++;
      continue;
    }
    if (c == ')')
    {
      if (depth == 0)
        return false;
      depth--;
      i++;
      continue;
    }
    if (isalpha(c) || c == '_' || c >= 0x80)
    {
      size_t start= i++;
      while (i < sql.size())
      {
        unsigned char token_char= (unsigned char) sql[i];
        if (!isalnum(token_char) && token_char != '_' && token_char != '$' &&
            token_char < 0x80)
          break;
        i++;
      }
      bool query_token= depth == 0 && is_query_token(sql, start, i - start);
      if (!strip_prefix)
      {
        if (!query_token)
          return false;
        source= sql;
        return true;
      }
      if (query_token)
      {
        source= sql.substr(start);
        return true;
      }
      continue;
    }
    if (!strip_prefix && depth == 0)
      return false;
    i++;
  }
  return false;
}

static bool has_duckdb_insert_target(THD *thd)
{
  if (thd->lex->sql_command != SQLCOM_INSERT_SELECT)
    return false;
  TABLE_LIST *target= thd->lex->query_tables;
  return !target || !target->table || target->table->file->ht == duckdb_hton;
}

static bool has_unsupported_insert_select_clauses(THD *thd)
{
  return thd->lex->sql_command == SQLCOM_INSERT_SELECT &&
         (thd->lex->duplicates == DUP_UPDATE || thd->lex->has_returning());
}

select_handler *create_duckdb_select_handler(THD *thd, SELECT_LEX *sel_lex,
                                             SELECT_LEX_UNIT *sel_unit)
{
  /*
    Only handle plain SELECT and INSERT ... SELECT for now.
  */

  if (thd->lex->sql_command != SQLCOM_SELECT &&
      thd->lex->sql_command != SQLCOM_INSERT_SELECT)
    return nullptr;

  if (!sel_lex || has_duckdb_insert_target(thd) ||
      has_unsupported_insert_select_clauses(thd))
    return nullptr;

  std::string query;
  if (!extract_source_query(thd, query))
    return nullptr;

  std::vector<std::string> external_tables;
  bool has_duckdb_table= false;

  if (!can_pushdown_to_duckdb(sel_lex, external_tables, has_duckdb_table))
    return nullptr;

  /* At least one DuckDB table must participate */
  if (!has_duckdb_table)
    return nullptr;

  /* Do not push down queries with side-effects (e.g. user variables) */
  if (sel_lex->uncacheable & UNCACHEABLE_SIDEEFFECT)
    return nullptr;

  auto *handler=
      new ha_duckdb_select_handler(thd, sel_lex, sel_unit, std::move(query));
  if (!external_tables.empty())
  {
    handler->set_cross_engine(std::move(external_tables));

    if (myduck::duckdb_log_options & LOG_DUCKDB_QUERY)
      sql_print_information("DuckDB: cross-engine pushdown with %zu "
                            "external table(s)",
                            handler->external_table_count());
  }
  return handler;
}

select_handler *create_duckdb_unit_handler(THD *thd, SELECT_LEX_UNIT *sel_unit)
{
  if ((thd->lex->sql_command != SQLCOM_SELECT &&
       thd->lex->sql_command != SQLCOM_INSERT_SELECT) ||
      has_duckdb_insert_target(thd) ||
      has_unsupported_insert_select_clauses(thd))
    return nullptr;

  if (thd->stmt_arena && thd->stmt_arena->is_stmt_prepare())
    return nullptr;

  if (!sel_unit)
    return nullptr;

  std::string query;
  if (!extract_source_query(thd, query))
    return nullptr;

  std::vector<std::string> external_tables;
  bool has_duckdb_table= false;

  if (!can_pushdown_unit_to_duckdb(sel_unit, external_tables,
                                   has_duckdb_table))
    return nullptr;

  if (!has_duckdb_table)
    return nullptr;

  auto *handler=
      new ha_duckdb_select_handler(thd, sel_unit, std::move(query));
  if (!external_tables.empty())
  {
    handler->set_cross_engine(std::move(external_tables));

    if (myduck::duckdb_log_options & LOG_DUCKDB_QUERY)
      sql_print_information("DuckDB: cross-engine UNION pushdown with %zu "
                            "external table(s)",
                            handler->external_table_count());
  }
  return handler;
}

/* ----- ha_duckdb_select_handler implementation ----- */

ha_duckdb_select_handler::ha_duckdb_select_handler(THD *thd_arg,
                                                   SELECT_LEX *sel_lex,
                                                   SELECT_LEX_UNIT *sel_unit,
                                                   std::string &&query)
    : select_handler(thd_arg, duckdb_hton, sel_lex, sel_unit),
      current_row_index(0), query_string(thd_arg->charset())
{
  query_string.length(0);
  query_string.append(query.c_str(), query.length());
}

ha_duckdb_select_handler::ha_duckdb_select_handler(THD *thd_arg,
                                                   SELECT_LEX_UNIT *sel_unit,
                                                   std::string &&query)
    : select_handler(thd_arg, duckdb_hton, sel_unit), current_row_index(0),
      query_string(thd_arg->charset())
{
  query_string.length(0);
  query_string.append(query.c_str(), query.length());
}

ha_duckdb_select_handler::~ha_duckdb_select_handler()= default;

void ha_duckdb_select_handler::set_cross_engine(
    std::vector<std::string> &&tables)
{
  has_cross_engine= true;
  external_table_names= std::move(tables);
}

size_t ha_duckdb_select_handler::external_table_count() const
{
  return external_table_names.size();
}

/**
  Case-insensitive find that skips single-quoted strings ('...'),
  double-quoted strings ("..."), backtick identifiers (`...`),
  line comments (-- ...) and C-style comments.
  Returns position of the first match at or after @p start,
  or std::string::npos.
*/

static size_t find_in_code(const std::string &sql, const char *pattern,
                           size_t start= 0)
{
  size_t plen= strlen(pattern);
  if (plen == 0 || sql.size() < plen)
    return std::string::npos;

  size_t i= start;
  while (i + plen <= sql.size())
  {
    unsigned char c= sql[i];

    /* Skip single-quoted strings: handle \' and '' escapes */
    if (c == '\'')
    {
      for (i++; i < sql.size(); i++)
      {
        if (sql[i] == '\\') { i++; continue; }
        if (sql[i] == '\'')
        {
          if (i + 1 < sql.size() && sql[i + 1] == '\'') { i++; continue; }
          break;
        }
      }
      i++;
      continue;
    }

    /* Skip double-quoted strings */
    if (c == '"')
    {
      for (i++; i < sql.size(); i++)
      {
        if (sql[i] == '\\') { i++; continue; }
        if (sql[i] == '"')
        {
          if (i + 1 < sql.size() && sql[i + 1] == '"') { i++; continue; }
          break;
        }
      }
      i++;
      continue;
    }

    /* Skip backtick identifiers */
    if (c == '`')
    {
      for (i++; i < sql.size(); i++)
      {
        if (sql[i] == '`')
        {
          if (i + 1 < sql.size() && sql[i + 1] == '`') { i++; continue; }
          break;
        }
      }
      i++;
      continue;
    }

    /* Skip -- line comments */
    if (c == '-' && i + 1 < sql.size() && sql[i + 1] == '-')
    {
      for (i += 2; i < sql.size() && sql[i] != '\n'; i++) {}
      continue;
    }

    /* Skip C-style comments */
    if (c == '/' && i + 1 < sql.size() && sql[i + 1] == '*')
    {
      for (i += 2; i + 1 < sql.size(); i++)
      {
        if (sql[i] == '*' && sql[i + 1] == '/') { i += 2; break; }
      }
      continue;
    }

    /* Case-insensitive match at position i */
    bool match= true;
    for (size_t j= 0; j < plen; j++)
    {
      if (toupper((unsigned char) sql[i + j]) !=
          toupper((unsigned char) pattern[j]))
      {
        match= false;
        break;
      }
    }
    if (match)
      return i;

    i++;
  }

  return std::string::npos;
}

/**
  Reverse find_in_code: returns position of the last match at or before
  @p before, or std::string::npos.
*/

static size_t rfind_in_code(const std::string &sql, const char *pattern,
                            size_t before= std::string::npos)
{
  size_t last= std::string::npos;
  size_t pos= 0;
  for (;;)
  {
    pos= find_in_code(sql, pattern, pos);
    if (pos == std::string::npos || pos > before)
      break;
    last= pos;
    pos++;
  }
  return last;
}

/**
  Case-insensitive prefix match: does @p sql at position @p pos start
  with @p prefix?
*/

static bool ci_starts_with(const std::string &sql, size_t pos,
                           const char *prefix)
{
  for (size_t i= 0; prefix[i]; i++)
  {
    if (pos + i >= sql.size())
      return false;
    if (toupper((unsigned char) sql[pos + i]) !=
        toupper((unsigned char) prefix[i]))
      return false;
  }
  return true;
}

int ha_duckdb_select_handler::init_scan()
{
  DBUG_ENTER("ha_duckdb_select_handler::init_scan");

  /* Register external tables with the thread-local replacement scan registry
   */

  if (has_cross_engine)
  {
    myduck::register_cross_engine_ryow(myduck::get_thd_cross_engine_ryow(thd));

    auto register_tables_from_sel= [this](SELECT_LEX *sl) {
      for (TABLE_LIST *tbl= sl->get_table_list(); tbl; tbl= tbl->next_global)
      {
        if (tbl->derived || !tbl->table)
          continue;
        if (tbl->table->file->ht != duckdb_hton)
        {
          myduck::register_external_table(tbl->table_name.str, tbl->table);

          if (sl->where)
          {
            COND *table_cond=
                make_cond_for_table(thd, sl->where, tbl->table->map,
                                    tbl->table->map, -1, false, false);
            if (table_cond)
            {
              StringBuffer<1024> buf;
              table_cond->print(&buf,
                  QT_ITEM_IDENT_DISABLE_DB_TABLE_NAMES);
              if (myduck::duckdb_log_options & LOG_DUCKDB_QUERY)
                sql_print_information(
                    "CROSS-ENGINE: WHERE for '%s': %.*s",
                    tbl->table_name.str, (int) buf.length(), buf.ptr());
              myduck::register_external_where(
                  tbl->table_name.str,
                  std::string(buf.ptr(), buf.length()));
            }
          }
        }
      }
    };

    if (select_lex)
    {
      register_tables_from_sel(select_lex);
    }
    else if (lex_unit)
    {
      for (SELECT_LEX *sl= lex_unit->first_select(); sl; sl= sl->next_select())
        register_tables_from_sel(sl);
    }
  }

  std::string sql(query_string.ptr(), query_string.length());

  /*
    Rewrite MariaDB-specific SQL syntax that DuckDB does not understand.
    GROUP BY ... WITH ROLLUP  →  GROUP BY ROLLUP(...)
  */

  {
    size_t search_from= 0;
    size_t rollup_pos;
    while ((rollup_pos= find_in_code(sql, " WITH ROLLUP", search_from))
           != std::string::npos)
    {
      size_t group_pos= rfind_in_code(sql, "GROUP BY ", rollup_pos);
      if (group_pos != std::string::npos && group_pos >= search_from)
      {
        size_t cols_start= group_pos + 9;
        std::string cols= sql.substr(cols_start, rollup_pos - cols_start);
        std::string replacement= "GROUP BY ROLLUP(" + cols + ")";
        sql.replace(group_pos, rollup_pos + 12 - group_pos, replacement);
        search_from= group_pos + replacement.size();
      }
      else
        search_from= rollup_pos + 12;
    }
  }

  /*
    Rewrite CONVERT(expr, TYPE) → CAST(expr AS TYPE)
    MariaDB uses CONVERT(expr, type) syntax, DuckDB uses CAST(expr AS type).
  */

  {
    size_t pos= 0;
    while ((pos= find_in_code(sql, "CONVERT(", pos)) != std::string::npos)
    {
      /* Word boundary: skip if preceded by an identifier character */
      if (pos > 0 &&
          (isalnum((unsigned char) sql[pos - 1]) || sql[pos - 1] == '_'))
      {
        pos++;
        continue;
      }
      /* Find matching closing paren, handling nested parens */
      size_t start= pos + 8; /* after "CONVERT(" */
      int depth= 1;
      size_t i= start;
      size_t comma= std::string::npos;
      for (; i < sql.size() && depth > 0; i++)
      {
        if (sql[i] == '(')
          depth++;
        else if (sql[i] == ')')
          depth--;
        else if (sql[i] == ',' && depth == 1 && comma == std::string::npos)
          comma= i;
      }
      if (comma != std::string::npos && depth == 0)
      {
        std::string expr= sql.substr(start, comma - start);
        std::string type= sql.substr(comma + 1, i - 1 - (comma + 1));
        /* Trim whitespace from type */
        size_t ts= type.find_first_not_of(" \t");
        size_t te= type.find_last_not_of(" \t");
        if (ts != std::string::npos)
          type= type.substr(ts, te - ts + 1);
        std::string replacement= "CAST(" + expr + " AS " + type + ")";
        sql.replace(pos, i - pos, replacement);
      }
      else
        pos++;
    }
  }

  /*
    Rewrite CURRENT_TIME() → current_time, CURRENT_DATE() → current_date,
    CURRENT_TIMESTAMP() → current_timestamp.
    MariaDB outputs these with parens; DuckDB treats them as keywords.
  */

  {
    static const char *time_kws[]= {
        "CURRENT_TIME(", "CURRENT_DATE(", "CURRENT_TIMESTAMP("};
    static const char *time_repls[]= {
        "current_time", "current_date", "current_timestamp"};
    for (int ki= 0; ki < 3; ki++)
    {
      size_t klen= strlen(time_kws[ki]);
      size_t pos= 0;
      while ((pos= find_in_code(sql, time_kws[ki], pos)) != std::string::npos)
      {
        /* Find closing paren */
        size_t end= sql.find(')', pos + klen);
        if (end == std::string::npos) { pos++; continue; }
        sql.replace(pos, end + 1 - pos, time_repls[ki]);
      }
    }
  }

  /*
    Handle STRAIGHT_JOIN: as a SELECT hint — remove; as a join keyword —
    replace with JOIN (preserves ON clause).
    Then rewrite remaining conditionless JOIN → CROSS JOIN.
  */

  {
    size_t pos= 0;
    while ((pos= find_in_code(sql, "STRAIGHT_JOIN", pos)) != std::string::npos)
    {
      /*
        Determine if STRAIGHT_JOIN is a SELECT modifier (hint) or a join
        keyword.  As a hint it follows SELECT/ALL/DISTINCT/DISTINCTROW.
        As a join it follows a table name, alias, or closing paren.
      */

      size_t p= pos;
      while (p > 0 && sql[p - 1] == ' ') p--;
      size_t word_end= p;
      while (p > 0 &&
             (isalnum((unsigned char) sql[p - 1]) || sql[p - 1] == '_'))
        p--;
      size_t wlen= word_end - p;
      bool is_hint=
          wlen > 0 &&
          ((wlen == 6 && ci_starts_with(sql, p, "SELECT")) ||
           (wlen == 3 && ci_starts_with(sql, p, "ALL")) ||
           (wlen == 8 && ci_starts_with(sql, p, "DISTINCT")) ||
           (wlen == 11 && ci_starts_with(sql, p, "DISTINCTROW")));

      if (is_hint)
      {
        size_t elen= 13;
        if (pos + elen < sql.size() && sql[pos + elen] == ' ')
          elen++;
        sql.erase(pos, elen);
      }
      else
        sql.replace(pos, 13, "JOIN");
    }
    /*
      Scan for remaining conditionless JOIN (no ON/USING).
    */

    pos= 0;
    while ((pos= find_in_code(sql, " JOIN ", pos)) != std::string::npos)
    {
      /* Skip qualified JOINs: LEFT/RIGHT/INNER/CROSS/NATURAL/FULL/OUTER */
      {
        size_t p= pos;
        while (p > 0 && sql[p - 1] == ' ') p--;
        size_t word_end= p;
        while (p > 0 && isalpha((unsigned char) sql[p - 1])) p--;
        size_t wlen= word_end - p;
        if (wlen > 0 &&
            ((wlen == 5 && ci_starts_with(sql, p, "CROSS")) ||
             (wlen == 7 && ci_starts_with(sql, p, "NATURAL")) ||
             (wlen == 4 && ci_starts_with(sql, p, "LEFT")) ||
             (wlen == 5 && ci_starts_with(sql, p, "RIGHT")) ||
             (wlen == 5 && ci_starts_with(sql, p, "INNER")) ||
             (wlen == 4 && ci_starts_with(sql, p, "FULL")) ||
             (wlen == 5 && ci_starts_with(sql, p, "OUTER"))))
        {
          pos+= 6;
          continue;
        }
      }

      /* Scan forward to find ON/USING or a clause boundary */
      size_t scan= pos + 6;
      bool has_condition= false;
      while (scan < sql.size())
      {
        /* Check for ON (as keyword, followed by space) */
        if (ci_starts_with(sql, scan, "ON ") ||
            ci_starts_with(sql, scan, "USING ") ||
            ci_starts_with(sql, scan, "USING("))
        {
          has_condition= true;
          break;
        }
        /* Clause boundary — no ON found, this is conditionless */
        if (ci_starts_with(sql, scan, "JOIN ") ||
            ci_starts_with(sql, scan, "WHERE ") ||
            ci_starts_with(sql, scan, "GROUP ") ||
            ci_starts_with(sql, scan, "ORDER ") ||
            ci_starts_with(sql, scan, "LIMIT ") ||
            ci_starts_with(sql, scan, "HAVING ") ||
            sql[scan] == ';')
          break;
        scan++;
      }
      if (!has_condition)
      {
        sql.replace(pos, 6, " CROSS JOIN ");
        pos+= 12;
      }
      else
        pos+= 6;
    }
  }

  /*
    Rewrite REGEXP / NOT REGEXP → regexp_matches().
    MariaDB: expr REGEXP pattern / expr NOT REGEXP pattern
    DuckDB:  regexp_matches(expr, pattern) / NOT regexp_matches(expr, pattern)
  */

  {
    /* Replace NOT REGEXP first (longer), then REGEXP */
    size_t pos= 0;
    while ((pos= find_in_code(sql, " NOT REGEXP ", pos)) != std::string::npos)
      sql.replace(pos, 12, " !~ ");
    pos= 0;
    while ((pos= find_in_code(sql, " REGEXP ", pos)) != std::string::npos)
      sql.replace(pos, 8, " ~ ");
    /* RLIKE is a synonym for REGEXP in MariaDB */
    pos= 0;
    while ((pos= find_in_code(sql, " NOT RLIKE ", pos)) != std::string::npos)
      sql.replace(pos, 11, " !~ ");
    pos= 0;
    while ((pos= find_in_code(sql, " RLIKE ", pos)) != std::string::npos)
      sql.replace(pos, 7, " ~ ");
  }

  /*
    Remove MariaDB-specific SELECT hints that DuckDB doesn't understand.
    HIGH_PRIORITY, SQL_NO_CACHE, SQL_CACHE, STRAIGHT_JOIN (as hint).
  */

  {
    static const char *hints[]= {
        "HIGH_PRIORITY ", "SQL_NO_CACHE ", "SQL_CACHE ",
        "SQL_BUFFER_RESULT ", "SQL_SMALL_RESULT ", "SQL_BIG_RESULT ",
        "SQL_CALC_FOUND_ROWS "};
    for (auto hint : hints)
    {
      size_t hlen= strlen(hint);
      size_t pos;
      while ((pos= find_in_code(sql, hint)) != std::string::npos)
        sql.erase(pos, hlen);
    }
  }

  /*
    Remove MariaDB index hints: FORCE INDEX(...), USE INDEX(...),
    IGNORE INDEX(...).
  */

  {
    static const char *idx_hints[]= {
        "FORCE INDEX(", "USE INDEX(", "IGNORE INDEX("};
    for (auto hint : idx_hints)
    {
      size_t hlen= strlen(hint);
      size_t pos= 0;
      while ((pos= find_in_code(sql, hint, pos)) != std::string::npos)
      {
        /* Find matching closing paren */
        size_t end= sql.find(')', pos + hlen);
        if (end == std::string::npos)
        {
          pos++;
          continue;
        }
        /* Remove including surrounding spaces */
        size_t erase_start= pos;
        size_t erase_end= end + 1;
        while (erase_end < sql.size() && sql[erase_end] == ' ')
          erase_end++;
        sql.erase(erase_start, erase_end - erase_start);
      }
    }
  }

  /*
    Rewrite LIMIT offset,count → LIMIT count OFFSET offset.
    MariaDB: LIMIT 2,5   DuckDB: LIMIT 5 OFFSET 2
  */

  {
    size_t lpos= 0;
    while ((lpos= find_in_code(sql, "LIMIT ", lpos)) != std::string::npos)
    {
      size_t after_limit= lpos + 6;
      /* Skip whitespace */
      while (after_limit < sql.size() && sql[after_limit] == ' ')
        after_limit++;
      /* Read first number */
      size_t num1_start= after_limit;
      while (after_limit < sql.size() && isdigit(sql[after_limit]))
        after_limit++;
      if (after_limit == num1_start)
      {
        lpos= after_limit;
        continue;
      }
      /* Check for comma */
      size_t comma= after_limit;
      while (comma < sql.size() && sql[comma] == ' ')
        comma++;
      if (comma < sql.size() && sql[comma] == ',')
      {
        std::string offset_str= sql.substr(num1_start,
                                           after_limit - num1_start);
        size_t num2_start= comma + 1;
        while (num2_start < sql.size() && sql[num2_start] == ' ')
          num2_start++;
        size_t num2_end= num2_start;
        while (num2_end < sql.size() && isdigit(sql[num2_end]))
          num2_end++;
        std::string count_str= sql.substr(num2_start, num2_end - num2_start);
        std::string replacement= "LIMIT " + count_str + " OFFSET " +
                                 offset_str;
        sql.replace(lpos, num2_end - lpos, replacement);
        lpos+= replacement.size();
      }
      else
        lpos= after_limit;
    }
  }

  query_result= myduck::duckdb_stream_query(thd, sql, true);

  if (!query_result || query_result->HasError())
  {
    if (query_result)
      my_error(ER_GET_ERRMSG, MYF(0), HA_ERR_INTERNAL_ERROR,
               query_result->GetError().c_str(), "DuckDB");
    else
      my_error(ER_GET_ERRMSG, MYF(0), HA_ERR_INTERNAL_ERROR,
               "DuckDB query returned null result", "DuckDB");
    DBUG_RETURN(HA_ERR_INTERNAL_ERROR);
  }

  current_chunk.reset();
  current_row_index= 0;

  DBUG_RETURN(0);
}

int ha_duckdb_select_handler::next_row()
{
  DBUG_ENTER("ha_duckdb_select_handler::next_row");

  if (!query_result)
    DBUG_RETURN(HA_ERR_INTERNAL_ERROR);

  /* Fetch a new chunk when the current one is exhausted */
  if (!current_chunk || current_row_index >= current_chunk->size())
  {
    current_chunk.reset();
    current_chunk= query_result->Fetch();

    if (!current_chunk || current_chunk->size() == 0)
    {
      if (query_result->HasError())
      {
        my_error(ER_GET_ERRMSG, MYF(0), HA_ERR_INTERNAL_ERROR,
                 query_result->GetError().c_str(), "DuckDB");
        DBUG_RETURN(HA_ERR_INTERNAL_ERROR);
      }
      DBUG_RETURN(HA_ERR_END_OF_FILE);
    }

    current_row_index= 0;
  }

  /*
    Store the fields into table->record[0].
    The select_handler framework provides `table` which is a temporary
    table with one Field per item in the select list.
  */

  size_t col_count= current_chunk->ColumnCount();
  size_t field_count= 0;

  for (Field **f= table->field; *f; f++)
    field_count++;

  size_t ncols= (col_count < field_count) ? col_count : field_count;

  for (size_t col_idx= 0; col_idx < ncols; col_idx++)
  {
    duckdb::Value value= current_chunk->GetValue(col_idx, current_row_index);
    Field *field= table->field[col_idx];
    store_duckdb_field_in_mysql_format(field, value, thd);
  }

  current_row_index++;
  DBUG_RETURN(0);
}

int ha_duckdb_select_handler::end_scan()
{
  DBUG_ENTER("ha_duckdb_select_handler::end_scan");

  if (has_cross_engine)
    myduck::clear_external_tables();

  current_chunk.reset();
  query_result.reset();
  current_row_index= 0;

  free_tmp_table(thd, table);
  table= 0;

  DBUG_RETURN(0);
}

Messung V0.5 in Prozent
C=96 H=74 G=85

¤ Dauer der Verarbeitung: 0.19 Sekunden  (vorverarbeitet am  2026-10-08) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1126864
#Domains=1897691