size_t MySQLParserServicesImpl::parseSQLIntoCatalog()

in modules/db.mysql.parser/src/mysql_parser_module.cpp [1412:2052]


size_t MySQLParserServicesImpl::parseSQLIntoCatalog(MySQLParserContext::Ref context, db_mysql_CatalogRef catalog,
                                                    const std::string &sql, grt::DictRef options) {
  MySQLParserContextImpl *impl = dynamic_cast<MySQLParserContextImpl *>(context.get());

  static std::set<MySQLQueryType> relevantQueryTypes = {
    QtAlterDatabase,
    QtAlterLogFileGroup,
    QtAlterFunction,
    QtAlterProcedure,
    QtAlterServer,
    QtAlterTable,
    QtAlterTableSpace,
    QtAlterEvent,
    QtAlterView,

    QtCreateTable,
    QtCreateIndex,
    QtCreateDatabase,
    QtCreateEvent,
    QtCreateView,
    QtCreateRoutine,
    QtCreateProcedure,
    QtCreateFunction,
    QtCreateUdf,
    QtCreateTrigger,
    QtCreateLogFileGroup,
    QtCreateServer,
    QtCreateTableSpace,

    QtDropDatabase,
    QtDropEvent,
    QtDropFunction,
    QtDropProcedure,
    QtDropIndex,
    QtDropLogfileGroup,
    QtDropServer,
    QtDropTable,
    QtDropTablespace,
    QtDropTrigger,
    QtDropView,

    QtRenameTable,

    QtUse
  };

  logDebug2("Parse sql into catalog\n");

  bool caseSensitive = impl->caseSensitive;

  std::string startSchema = options.get_string("schema");
  db_mysql_SchemaRef currentSchema;
  if (!startSchema.empty())
    currentSchema = ObjectListener::ensureSchemaExists(catalog, startSchema, caseSensitive);

  bool defaultSchemaCreated = false;
  bool autoGenerateFkNames = options.get_int("gen_fk_names_when_empty") != 0;
  // bool reuseExistingObjects = options.get_int("reuse_existing_objects") != 0;

  if (!currentSchema.is_valid()) {
    currentSchema = db_mysql_SchemaRef::cast_from(catalog->defaultSchema());
    if (!currentSchema.is_valid()) {
      db_SchemaRef df = find_named_object_in_list(catalog->schemata(), "default_schema", caseSensitive);
      if (!df.is_valid())
        defaultSchemaCreated = true;
      currentSchema = ObjectListener::ensureSchemaExists(catalog, "default_schema", caseSensitive);
    }
  }

  size_t errorCount = 0;
  std::vector<StatementRange> ranges;
  determineStatementRanges(sql.c_str(), sql.size(), ";", ranges, "\n");

  grt::ListRef<GrtObject> createdObjects = grt::ListRef<GrtObject>::cast_from(options.get("created_objects"));
  if (!createdObjects.is_valid()) {
    createdObjects = grt::ListRef<GrtObject>(grt::Initialized);
    options.set("created_objects", createdObjects);
  }

  StringListRef errors = StringListRef::cast_from(options.get("errors"));

  // Collect textual FK references into a local cache. At the end this is used
  // to find actual ref tables + columns, when all tables have been parsed.
  DbObjectsRefsCache refCache;
  for (auto &range : ranges) {
    std::string query(sql.c_str() + range.start, range.length);
    MySQLQueryType queryType = impl->determineQueryType(query);

    if (relevantQueryTypes.count(queryType) == 0)
      continue; // Something we are not interested in. Don't bother parsing it.

    auto tree = impl->parse(query, MySQLParseUnit::PuGeneric);
    if (!impl->errors.empty()) {
      errorCount += impl->errors.size();
      if (errors.is_valid()) {
        for (auto &error : impl->errors)
          errors.insert("(" + std::to_string(range.line) + ", " + std::to_string(error.offset) + ") "
                        + error.message);
      }
      continue;
    }

    auto statementContext = dynamic_cast<MySQLParser::QueryContext *>(tree)->simpleStatement();
    switch (queryType) {
      case QtCreateDatabase: {
        db_mysql_SchemaRef schema(grt::Initialized);
        schema->createDate(base::fmttime(0, DATETIME_FMT));
        schema->lastChangeDate(schema->createDate());
        schema->owner(catalog);

        SchemaListener listener(statementContext, catalog, schema, impl->caseSensitive);
        schema->oldName(schema->name());

        db_SchemaRef existing = find_named_object_in_list(catalog->schemata(), schema->name(), caseSensitive);
        if (existing.is_valid()) {
          if (!listener.ignoreIfExists) {
            catalog->schemata()->remove(existing);
            createdObjects.remove_value(existing);

            catalog->schemata().insert(schema);
            createdObjects.insert(schema);
          }
        } else {
          catalog->schemata().insert(schema);
          createdObjects.insert(schema);
        }

        break;
      }

      case QtUse: {
        std::string schemaName =
          base::unquote(statementContext->utilityStatement()->useCommand()->identifier()->getText());
        currentSchema = ObjectListener::ensureSchemaExists(catalog, schemaName, caseSensitive);
        break;
      }

      case QtCreateTable: {
        db_mysql_TableRef table(grt::Initialized);
        table->createDate(base::fmttime(0, DATETIME_FMT));
        table->lastChangeDate(table->createDate());
        table->owner(currentSchema);

        TableListener listener(statementContext, catalog, currentSchema, table, caseSensitive, autoGenerateFkNames,
                               refCache);
        table->oldName(table->name());

        db_mysql_SchemaRef schema =
          db_mysql_SchemaRef::cast_from(table->owner()); // Might be different from current schema.

        // Ignore tables that use a name that is already used for a view (no drop/new-add takes place then).
        db_mysql_ViewRef existingView = find_named_object_in_list(schema->views(), table->name());
        if (!existingView.is_valid()) {
          db_TableRef existingTable = find_named_object_in_list(schema->tables(), table->name());
          if (existingTable.is_valid()) {
            // Ignore if the table exists already?
            if (!listener.ignoreIfExists) {
              schema->tables()->remove(existingTable);
              createdObjects.remove_value(existingTable);

              schema->tables().insert(table);
              createdObjects.insert(table);
            }
          } else {
            schema->tables().insert(table);
            createdObjects.insert(table);
          }
        }

        break;
      }

      case QtCreateIndex: {
        db_mysql_IndexRef index(grt::Initialized);
        index->createDate(base::fmttime(0, DATETIME_FMT));
        index->lastChangeDate(index->createDate());

        IndexListener listener(statementContext, catalog, currentSchema, index, impl->caseSensitive, refCache);
        index->oldName(index->name());

        db_TableRef table = db_TableRef::cast_from(index->owner());
        if (table.is_valid()) {
          db_IndexRef existing = find_named_object_in_list(table->indices(), index->name());
          if (existing.is_valid()) {
            table->indices()->remove(existing);
            createdObjects.remove_value(existing);
          }
          table->indices().insert(index);
          createdObjects.insert(index);
        }

        break;
      }

      case QtCreateEvent: {
        db_mysql_EventRef event(grt::Initialized);
        event->sqlDefinition(base::trim(query));
        event->createDate(base::fmttime(0, DATETIME_FMT));
        event->lastChangeDate(event->createDate());
        event->owner(currentSchema);

        EventListener listener(statementContext, catalog, event, impl->caseSensitive);
        event->oldName(event->name());

        db_mysql_SchemaRef schema =
          db_mysql_SchemaRef::cast_from(event->owner()); // Might be different from current schema.
        db_EventRef existing = find_named_object_in_list(schema->events(), event->name());
        if (existing.is_valid()) {
          if (!listener.ignoreIfExists) // Ignore if exists?
          {
            schema->events()->remove(existing);
            createdObjects.remove_value(existing);

            schema->events().insert(event);
            createdObjects.insert(event);
          }
        } else {
          schema->events().insert(event);
          createdObjects.insert(event);
        }

        break;
      }

      case QtCreateView: {
        db_mysql_ViewRef view(grt::Initialized);
        view->sqlDefinition(base::trim(base::trim(query)));
        view->createDate(base::fmttime(0, DATETIME_FMT));
        view->lastChangeDate(view->createDate());
        view->owner(currentSchema);

        ViewListener listener(statementContext, catalog, view, caseSensitive);
        view->oldName(view->name());

        db_mysql_SchemaRef schema =
          db_mysql_SchemaRef::cast_from(view->owner()); // Might be different from current schema.

        // Ignore views that use a name that is already used for a table (no drop/new-add takes place then).
        db_mysql_TableRef existingTable = find_named_object_in_list(schema->tables(), view->name());
        if (!existingTable.is_valid()) {
          db_mysql_ViewRef existingView = find_named_object_in_list(schema->views(), view->name());
          if (existingView.is_valid()) {
            schema->views()->remove(existingView);
            createdObjects.remove_value(existingView);
          }
          schema->views().insert(view);
          createdObjects.insert(view);
        }

        break;
      }

      case QtCreateProcedure:
      case QtCreateFunction:
      case QtCreateUdf: {
        db_mysql_RoutineRef routine(grt::Initialized);
        routine->owner(currentSchema);
        routine->sqlDefinition(base::trim(query));
        routine->createDate(base::fmttime(0, DATETIME_FMT));
        routine->lastChangeDate(routine->createDate());

        RoutineListener listener(statementContext, catalog, routine, caseSensitive);
        routine->oldName(routine->name());

        db_mysql_SchemaRef schema =
          db_mysql_SchemaRef::cast_from(routine->owner()); // Might be different from current schema.

        db_RoutineRef existing = find_named_object_in_list(schema->routines(), routine->name());
        if (existing.is_valid()) {
          schema->routines()->remove(existing);
          createdObjects.remove_value(existing);
        }
        schema->routines().insert(routine);
        createdObjects.insert(routine);

        break;
      }

      case QtCreateTrigger: {
        db_mysql_TriggerRef trigger(grt::Initialized);
        trigger->sqlDefinition(base::trim(query));
        trigger->createDate(base::fmttime(0, DATETIME_FMT));
        trigger->lastChangeDate(trigger->createDate());

        TriggerListener listener(statementContext, catalog, currentSchema, trigger, caseSensitive);
        trigger->oldName(trigger->name());

        // It could be the listener had to create stub table. We have to add this to our created objects list.
        db_mysql_TableRef table = db_mysql_TableRef::cast_from(trigger->owner());
        if (table->isStub())
          createdObjects.insert(table);

        db_TriggerRef existing = find_named_object_in_list(table->triggers(), trigger->name());
        if (existing.is_valid()) {
          table->triggers()->remove(existing);
          createdObjects.remove_value(existing);
        }
        table->triggers().insert(trigger);
        createdObjects.insert(trigger);

        break;
      }

      case QtCreateLogFileGroup: {
        db_mysql_LogFileGroupRef group(grt::Initialized);
        group->createDate(base::fmttime(0, DATETIME_FMT));
        group->lastChangeDate(group->createDate());
        group->owner(catalog);

        LogfileGroupListener listener(statementContext, catalog, group, impl->caseSensitive);
        group->oldName(group->name());

        db_LogFileGroupRef existing = find_named_object_in_list(catalog->logFileGroups(), group->name());
        if (existing.is_valid()) {
          catalog->logFileGroups()->remove(existing);
          createdObjects.remove_value(existing);
        }

        catalog->logFileGroups().insert(group);
        createdObjects.insert(group);

        break;
      }

      case QtCreateServer: {
        db_mysql_ServerLinkRef server(grt::Initialized);
        server->createDate(base::fmttime(0, DATETIME_FMT));
        server->lastChangeDate(server->createDate());
        server->owner(catalog);

        ServerListener listener(statementContext, catalog, server, impl->caseSensitive);
        server->oldName(server->name());

        db_ServerLinkRef existing = find_named_object_in_list(catalog->serverLinks(), server->name());
        if (existing.is_valid()) {
          catalog->serverLinks()->remove(existing);
          createdObjects.remove_value(existing);
        }
        catalog->serverLinks().insert(server);
        createdObjects.insert(server);

        break;
      }

      case QtCreateTableSpace: {
        db_mysql_TablespaceRef tablespace(grt::Initialized);
        tablespace->createDate(base::fmttime(0, DATETIME_FMT));
        tablespace->lastChangeDate(tablespace->createDate());
        tablespace->owner(catalog);

        TablespaceListener listener(statementContext, catalog, tablespace, impl->caseSensitive);
        tablespace->oldName(tablespace->name());

        db_TablespaceRef existing = find_named_object_in_list(catalog->tablespaces(), tablespace->name());
        if (existing.is_valid()) {
          catalog->tablespaces()->remove(existing);
          createdObjects.remove_value(existing);
        }
        catalog->tablespaces().insert(tablespace);
        createdObjects.insert(tablespace);

        break;
      }

      case QtDropDatabase: {
        std::string name = base::unquote(statementContext->dropStatement()->dropDatabase()->schemaRef()->getText());
        db_SchemaRef schema = find_named_object_in_list(catalog->schemata(), name);
        if (schema.is_valid()) {
          catalog->schemata()->remove(schema);
          createdObjects.remove_value(schema);

          if (catalog->defaultSchema() == schema)
            catalog->defaultSchema(db_mysql_SchemaRef());
          if (currentSchema == schema)
            currentSchema = db_mysql_SchemaRef::cast_from(catalog->defaultSchema());
          if (!currentSchema.is_valid())
            currentSchema = ObjectListener::ensureSchemaExists(catalog, "default_schema", caseSensitive);
        }
        break;
      }

      case QtDropEvent: {
        IdentifierListener listener(statementContext->dropStatement()->dropEvent()->eventRef());

        db_SchemaRef schema = currentSchema;
        if (listener.parts.size() > 1 && !listener.parts[0].empty())
          schema = ObjectListener::ensureSchemaExists(catalog, listener.parts[0], caseSensitive);
        db_EventRef event = find_named_object_in_list(schema->events(), listener.parts.back());
        if (event.is_valid()) {
          schema->events()->remove(event);
          createdObjects.remove_value(event);
        }

        break;
      }

      case QtDropProcedure:
      case QtDropFunction: // Including UDFs.
      {
        tree::ParseTree *nameContext;
        if (queryType == QtDropFunction)
          nameContext = statementContext->dropStatement()->dropFunction()->functionRef();
        else
          nameContext = statementContext->dropStatement()->dropProcedure()->procedureRef();
        IdentifierListener listener(nameContext);

        db_SchemaRef schema = currentSchema;
        if (listener.parts.size() > 1 && !listener.parts[0].empty())
          schema = ObjectListener::ensureSchemaExists(catalog, listener.parts[0], caseSensitive);
        db_RoutineRef routine = find_named_object_in_list(schema->routines(), listener.parts.back());
        if (routine.is_valid()) {
          schema->routines()->remove(routine);
          createdObjects.remove_value(routine);
        }

        break;
      }

      case QtDropIndex: {
        std::string name;
        {
          IdentifierListener listener(statementContext->dropStatement()->dropIndex()->indexRef());
          name = listener.parts.back();
        }

        IdentifierListener listener(statementContext->dropStatement()->dropIndex()->tableRef());

        db_SchemaRef schema = currentSchema;
        if (listener.parts.size() > 1 && !listener.parts[0].empty())
          schema = ObjectListener::ensureSchemaExists(catalog, listener.parts[0], caseSensitive);
        db_TableRef table = find_named_object_in_list(schema->tables(), listener.parts.back());
        if (table.is_valid()) {
          db_IndexRef index = find_named_object_in_list(table->indices(), name);
          if (index.is_valid()) {
            table->indices()->remove(index);
            createdObjects.remove_value(index);
          }
        }
        break;
      }

      case QtDropLogfileGroup: {
        IdentifierListener listener(statementContext->dropStatement()->dropLogfileGroup()->logfileGroupRef());

        db_LogFileGroupRef group = find_named_object_in_list(catalog->logFileGroups(), listener.parts.back());
        if (group.is_valid()) {
          catalog->logFileGroups()->remove(group);
          createdObjects.remove_value(group);
        }

        break;
      }

      case QtDropServer: {
        IdentifierListener listener(statementContext->dropStatement()->dropServer()->serverRef());
        db_ServerLinkRef server = find_named_object_in_list(catalog->serverLinks(), listener.parts.back());
        if (server.is_valid()) {
          catalog->serverLinks()->remove(server);
          createdObjects.remove_value(server);
        }

        break;
      }

      case QtDropTable: {
        // We can have a list of tables to drop here.
        for (auto tableRef : statementContext->dropStatement()->dropTable()->tableRefList()->tableRef()) {
          IdentifierListener listener(tableRef);
          db_SchemaRef schema = currentSchema;
          if (listener.parts.size() > 1 && !listener.parts[0].empty())
            schema = ObjectListener::ensureSchemaExists(catalog, listener.parts[0], caseSensitive);

          db_TableRef table = find_named_object_in_list(schema->tables(), listener.parts.back());
          if (table.is_valid()) {
            schema->tables()->remove(table);
            createdObjects.remove_value(table);
          }
        }

        break;
      }

      case QtDropView: {
        // We can have a list of views to drop here.
        for (auto tableRef : statementContext->dropStatement()->dropView()->viewRefList()->viewRef()) {
          IdentifierListener listener(tableRef);
          db_SchemaRef schema = currentSchema;
          if (listener.parts.size() > 1 && !listener.parts[0].empty())
            schema = ObjectListener::ensureSchemaExists(catalog, listener.parts[0], caseSensitive);

          db_ViewRef view = find_named_object_in_list(schema->views(), listener.parts.back());
          if (view.is_valid()) {
            schema->views()->remove(view);
            createdObjects.remove_value(view);
          }
        }

        break;
      }

      case QtDropTablespace: {
        IdentifierListener listener(statementContext->dropStatement()->dropTableSpace()->tablespaceRef());
        db_TablespaceRef tablespace = find_named_object_in_list(catalog->tablespaces(), listener.parts.back());
        if (tablespace.is_valid()) {
          catalog->tablespaces()->remove(tablespace);
          createdObjects.remove_value(tablespace);
        }

        break;
      }

      case QtDropTrigger: {
        IdentifierListener listener(statementContext->dropStatement()->dropTrigger()->triggerRef());

        // Even though triggers are schema level objects they work on specific tables
        // and that's why we store them under the affected tables, not in the schema object.
        // This however makes it more difficult to find the trigger to delete, as we have to
        // iterate over all tables.
        db_SchemaRef schema = currentSchema;
        if (listener.parts.size() > 1 && !listener.parts[0].empty())
          schema = ObjectListener::ensureSchemaExists(catalog, listener.parts[0], caseSensitive);

        for (auto table : schema->tables()) {
          db_TriggerRef trigger = find_named_object_in_list(table->triggers(), listener.parts.back());
          if (trigger.is_valid()) {
            table->triggers()->remove(trigger);
            createdObjects.remove_value(trigger);

            break; // A trigger can only be assigned to a single table, so we can stop here.
          }
        }
        break;
      }

      case QtRenameTable: {
        // Renaming a table is special as you can use it also to rename a view and to move
        // a table from one schema to the other (not for views, though).
        // Due to the way we store triggers we have an easy life wrt. related triggers.
        for (auto renamePair : statementContext->renameTableStatement()->renamePair()) {
          IdentifierListener sourceListener(renamePair->tableRef());

          db_SchemaRef sourceSchema = currentSchema;
          if (sourceListener.parts.size() > 1 && !sourceListener.parts[0].empty())
            sourceSchema = ObjectListener::ensureSchemaExists(catalog, sourceListener.parts[0], caseSensitive);

          IdentifierListener targetListener(renamePair->tableName());
          db_SchemaRef targetSchema = currentSchema;
          if (targetListener.parts.size() > 1 && !targetListener.parts[0].empty())
            targetSchema = ObjectListener::ensureSchemaExists(catalog, targetListener.parts[0], caseSensitive);

          db_ViewRef view = find_named_object_in_list(sourceSchema->views(), sourceListener.parts.back());
          if (view.is_valid()) {
            // Cannot move between schemas.
            if (sourceSchema == targetSchema)
              view->name(targetListener.parts.back());
          } else {
            // Renaming a table.
            db_TableRef table = find_named_object_in_list(sourceSchema->tables(), sourceListener.parts.back());
            if (table.is_valid()) {
              if (sourceSchema != targetSchema) {
                sourceSchema->tables()->remove(table);
                targetSchema->tables().insert(table);
                createdObjects.insert(table);
              }
              table->name(targetListener.parts.back());
            }
          }
        }

        break;
      }

      // Alter commands. At the moment we only support a limited number of cases as we mostly
      // need SQL-to-GRT conversion for create scripts.
      case QtAlterDatabase: {
        IdentifierListener listener(statementContext->alterStatement()->alterDatabase()->schemaRef());

        db_mysql_SchemaRef schema = ObjectListener::ensureSchemaExists(catalog, listener.parts.back(), caseSensitive);
        schema->lastChangeDate(base::fmttime(0, DATETIME_FMT));

        SchemaListener(statementContext, catalog, schema, impl->caseSensitive);
        break;
      }

      case QtAlterLogFileGroup:
        break;

      case QtAlterFunction:
        break;

      case QtAlterProcedure:
        break;

      case QtAlterServer:
        break;

      case QtAlterTable: { // Alter table only for adding/removing indices and for renames.
        IdentifierListener listener(statementContext->alterStatement()->alterTable()->tableRef());

        db_mysql_SchemaRef schema = currentSchema;
        if (listener.parts.size() > 1 && !listener.parts[0].empty())
          schema = ObjectListener::ensureSchemaExists(catalog, listener.parts[0], caseSensitive);

        db_mysql_TableRef table = find_named_object_in_list(schema->tables(), listener.parts.back(), caseSensitive);
        if (table.is_valid())
          TableAlterListener(statementContext, catalog, table, caseSensitive, autoGenerateFkNames, refCache);
        else {
          db_mysql_ViewRef view = find_named_object_in_list(schema->views(), listener.parts.back(), caseSensitive);
          if (view.is_valid())
            TableAlterListener(statementContext, catalog, view, caseSensitive, autoGenerateFkNames, refCache);
        }
        break;
      }

      case QtAlterTableSpace:
        break;

      case QtAlterEvent:
        break;

      case QtAlterView:
        break;

      default:
        continue; // Ignore anything else.
    }
  }

  resolveReferences(catalog, refCache, context->isCaseSensitive());

  // Remove the default_schema we may have created at the start, if it is empty.
  if (defaultSchemaCreated) {
    currentSchema = ObjectListener::ensureSchemaExists(catalog, "default_schema", caseSensitive);
    if (currentSchema->tables().count() == 0 && currentSchema->views().count() == 0 &&
        currentSchema->routines().count() == 0 && currentSchema->synonyms().count() == 0 &&
        currentSchema->sequences().count() == 0 && currentSchema->events().count() == 0)
      catalog->schemata().remove_value(currentSchema);
  }

  return errorCount;
}